Boarding.js

Light-weight, no-dependency, vanilla JavaScript engine to drive user's focus across the page

With Animation Without Animation

💡 This library is a a port of driver.js. The reason behind refactoring that library, is because driver.js under the hood works by elevating elements using css with z-index. This means it can very easily break your layout if you rely anywhere on the css stacking context.

So how does this library approach the problem? It doesn't touch any styles at all. Instead the library renders the overlay using an SVG element. This means it works with any layout.

It has the small downside that an SVG is less customizable in terms of how it looks visually than the approach that driver.js had. But maybe better support for styling will be added in the future, since there are other ways to approach that problem.

A lightweight (~7.5kb gzipped) yet powerful JavaScript engine that helps you drive the user's focus on page.

Some sample use-cases can be creating powerful feature introductions, call-to-action components, focus shifters etc.

What are the features?

Boarding is compatible with all the major browsers and can be used for any of your overlay needs. Feature introductions, focus shifters, call-to-action are just a few examples.


How does it do that?

In it simplest, it puts the canvas on top of the whole page and then cuts the part that is over the element to be highlighted and provides you several hooks when highlighting, highlighted or un-highlighting elements making it highly customizable.

How does it compare to driver.js?

Features Boarding.js Driver.js
Works with complex layouts
(no z-index conflicts)
Define steps for elements which are not mounted yet
(for highly interactive websites, such as react applications, where elements might only appear once an action has been taken)
Has the concept of a stage
(a customizable background for the highlighted element)

* Boarding.js will add the boarding-highlighted-element class to the higlighted element for optional custom styling.
*
Auto popover positioning
Control popover alignment
Control allowed user (click) actions
(strictClickHandling)
Allow preparing/validating a move before it happens
(prepareElement)
Written in TypeScript

Besides the above, Boarding.js also has a few more options available for each individual step, a more stable event system and more hooks for it, such as onBeforeHighlighted.


Can you show some Examples?

Below you find some of the examples and sample use-cases on how you can use it.

Highlighting a Single Element – Without Popover

If all you want is just highlight a single element, you can do that simply by passing the selector

Show Demo
const boarding = new Boarding();
boarding.highlight('#create-post');

A real world use-case for this could be highlighting an element when user is interacting with it

const focusBoarding = new Boarding();

// Highlight the section on focus
document.getElementById('creation-input')
  .addEventListener('focus', (e) => {
    focusBoarding.focus('#creation-input');
  });

Focus any of the inputs and see how it moves the highlight from one element to the other

You can also turn off the animation or set the padding around the corner. More on it later.


Highlighting a Single Element – With Popover

If you would like to show some details alongside the highlighted element, you can do that easily by specifying title and description

Show Demo
const boarding = new Boarding();
boarding.highlight({
  element: '#some-element',
  popover: {
    title: 'Title for the Popover',
    description: 'Description for it',
  }
});

You can modify the behavior of this popover also by a certain set of options. More on it below.


Popover Positioning

You can also, change the preffered position of the popover to be either top, bottom, left, right.

And for even more fine-grain control define the alignment of the popover to be either start, center, end

Show Demo
const boarding = new Boarding();
boarding.highlight({
  element: '#some-element',
  popover: {
    title: 'Title for the Popover',
    description: 'Description for it',
    // prefferedPosition can be top, bottom, left, right
    prefferedPosition: 'left',
    // alignment can be start, center, end
    alignment: "start"
  }
});

If you don't specify the position or specify it to be auto, it will automatically find the suitable position for the popover and show it


HTML in Popovers

You can also specify HTML in the body or the title of the popovers. Here is an example:

Show Demo
const boarding = new Boarding();
boarding.highlight({
  element: '#some-element',
  popover: {
    title: '<em>An italicized title</em>',
    description: 'Description may also contain <strong>HTML</strong>'
  }
});

And of course it can be any valid HTML.


Disable Close on Outside Click

By default, boarding.js gets reset if user clicks outside the highlighted element, you can disable this:

Show Demo
const boarding = new Boarding({
    allowClose: false,
});

boarding.highlight({
  element: '#some-element',
  popover: {
    title: '<em>An italicized title</em>',
    description: 'Description may also contain <strong>HTML</strong>'
  }
});

If you use this option, for multi-step boarding, it would close after you are done with the popover or you can close it programmatically. For single element popover, you need to close it properly, otherwise it won't be closed

boarding.reset()
        

Creating Feature Introductions

You can also make powerful feature introductions to guide the users about the features. Just provide an array of steps where each step specifies an element to highlight.

Below is just a quick example showing you how to combine the steps in introduction.

Show Demo
const boarding = new Boarding();
// Define the steps for introduction
boarding.defineSteps([
  {
    element: '#first-element-introduction',
    popover: {
      className: 'first-step-popover-class',
      title: 'Title on Popover',
      description: 'Body of the popover',
      prefferedPosition: 'left'
    }
  },
  {
    element: '#second-element-introduction',
    popover: {
      title: 'Title on Popover',
      description: 'Body of the popover',
      prefferedPosition: 'top'
    }
  },
  {
    element: '#third-element-introduction',
    popover: {
      title: 'Title on Popover',
      description: 'Body of the popover',
      prefferedPosition: 'right'
    }
  },
]);
// Start the introduction
boarding.start();

This is just a quick example for the feature introduction. For a richer one, please have a look at the "Quick Tour"

You may also turn off the footer buttons in popover, in which case user can control the popover using the arrows keys on keyboard. Or you may control it using the methods provided by Boarding.


Without Overlay

You can create feature introductions and do everything listed above without overlays also. All you have to do is just set the opacity to `0`.

Show Demo
const boarding = new Boarding({
    opacity: 0,
});

boarding.highlight({
  element: '#run-element-without-popover',
  popover: {
    title: 'Title for the Popover',
    description: 'Description for it',
    prefferedPosition: 'top', // can be `top`, `left`, `right`, `bottom`
  }
});

..and you can do the same for the feature introductions


With a current step counter

If you want to customize the rendered popover, for example by adding a a counter for the "current step", you can do so by modifying the popover html elements using onPopoverRender.

Show Demo
const boarding = new Boarding({
  // modify the rendered popoverElements.popoverTitle
  onPopoverRender: (popoverElements) => {
    // setTimeout, so we the count runs immediatly after all internal boarding logic has run. Otherwise we would get an outdated boarding.currentStep number
    setTimeout(() => {
      popoverElements.popoverTitle.innerText = `${
        popoverElements.popoverTitle.innerText
      } (${boarding.currentStep + 1}/${
        boarding.getSteps().length
      })`;
    }, 0);
  },
});

boarding.defineSteps([/* ...steps */]);
boarding.start();

..and you can do the same for the feature introductions


..and much much more

Boarding comes with many options that you can manipulate to make boarding behave as you may like

Boarding Definition

Here are the options that Boarding understands

const boarding = new Boarding({
  className: 'scoped-class', // className to wrap boarding.js popover
  animate: true, // Animate while changing highlighted element
  opacity: 0.75, // Background opacity (0 means only popovers and without overlay)
  padding: 10, // Distance of element from around the edges
  allowClose: true, // Whether clicking on overlay should close or not
  overlayClickNext: false, // Should it move to next step on overlay click
  doneBtnText: 'Done', // Text on the final button
  closeBtnText: 'Close', // Text on the close button for this step
  nextBtnText: 'Next', // Next button text for this step
  prevBtnText: 'Previous', // Previous button text for this step
  showButtons: false, // Do not show control buttons in footer
  keyboardControl: true, // Allow controlling through keyboard (escape to close, arrow keys to move)
  scrollIntoViewOptions: {
    behaviour: "smooth",
  }, // We use `scrollIntoView()` when possible, pass here the options for it if you want any. Alternatively, you can also disable this functionallity by setting scrollIntoViewOptions to "no-scroll"
  onBeforeHighlighted: (Element) {}, // Called when element is about to be highlighted
  onHighlighted: (Element) {}, // Called when element is fully highlighted
  onDeselected: (Element) {}, // Called when element has been deselected
  onReset: (Element) {}, // Called when overlay is about to be cleared
  onStart: (Element) {}, // Called when `boarding.start()` was called
  onNext: (Element) => {}, // Called when moving to next step on any step
  onPrevious: (Element) => {}, // Called when moving to next step on any step
  strictClickHandling: "block-all", // Can also be `true` or if not wanted at all, `false`. Either block ALL pointer events, or isolate pointer-events to only allow on the highlighted element (`true`). Popover and overlay pointer-events are of course always allowed to be clicked
  // Make changes to the actual popoverElements once they get rendered.
  onPopoverRender: (el) => {
    // ...
  },

});

Note that all the button options that you provide in the boarding definition can be overridden for a specific step by giving them in the step definition

Step Definition

Here are the set of options that you can pass in each step i.e. an item in array of steps or the object that you pass to highlight method

const stepDefinition = {
  element: '#some-item',        // Query selector string or Node to be highlighted
  popover: {                    // There will be no popover if empty or not given
    className: 'popover-class', // className to wrap this specific step popover in addition to the general className in Boarding options
    title: 'Title',             // Title on the popover
    description: 'Description', // Body of the popover
    showButtons: false,         // Do not show control buttons in footer
    closeBtnText: 'Close',      // Text on the close button for this step
    nextBtnText: 'Next',        // Next button text for this step
    prevBtnText: 'Previous',    // Previous button text for this step
    onPopoverRender: (el) => {  // Make changes to the actual popoverElements once they get rendered.
      // ...
    }, 
  }
  prepareElement: () => {},     // Called *before* moving to this step (for both cases when coming from "onNext" or "onPrevious")
  onNext: (Element) => {},      // Overwrite the original onX eventhandlers for the current step. Same for on[Previous/Highlighted/BeforeHighlighted/Deselected]
};

API Methods

Below are the set of methods that are available to you

const boarding = new Boarding(boardingOptions);

const isActivated = boarding.isActivated; // Checks if the boarding is active or not
boarding.next(); // Moves to next step in the steps list
boarding.previous(); // Moves to previous step in the steps list
boarding.start(stepNumber = 0); // Starts driving through the defined steps
boarding.highlight(string|stepDefinition); // highlights the element using query selector or the step definition
boarding.reset(); // Resets the overlay and clears the screen
boarding.hasHighlightedElement(); // Checks if there is any highlighted element
boarding.hasNextStep(); // Checks if there is next step to move to
boarding.hasPreviousStep(); // Checks if there is previous step to move to

// Prevents the current move. Useful in `prepareElement`, `onNext`, `onPrevious` if you want to
// perform some asynchronous task and manually move to next step
boarding.preventMove();
boarding.continue(); // Continue the move that was prevented using preventMove
boarding.clearMovePrevented(); // preventMove will just "pause" the tour. If you want to clear that paused state, you can call clearMovePrevented, to clean up that paused state. You should do this, when your logic has a condition where it never calls continue

// Gets the currently highlighted element on screen
const activeElement = boarding.getHighlightedElement();
const lastActiveElement = boarding.getLastHighlightedElement();
activeElement.getCalculatedPosition(); // Gets screen co-ordinates of the active element
activeElement.hidePopover();  // Hide the popover
activeElement.showPopover();  // Show the popover

activeElement.getNode();  // Gets the DOM Element behind this element

You can use a variety of options to achieve whatever you may want. I have some plans on improving it further, make sure to keep an eye on the GitHub page


Contributing

You can find the contribution instructions on the GitHub page. Feel free to submit an issue, create a pull request or spread the word

Follow @josias-r