Scrollspy

Scrollspy

Highlight the table of contents as the page scrolls.

Scrollspy watches a set of sections and which one is currently in view. The table of contents on the right of every documentation page is the live demo: the matching link gets active and aria-current="true". Clicking a link is a normal hash jump — the offset is scroll-margin, the motion is scroll-behavior: smooth.

Put scrollspy and an id on each section. The TOC is a set of destinations within the page, so it lives in a labelled <nav>; put table-of-contents on its list and point each link at #that-id. AutoInit() starts every .scrollspy except those marked no-autoinit. Observation is IntersectionObserver — there is no scroll listener.

<div class="row">
  <div class="s12 m9">
    <div id="introduction" class="section scrollspy">
      <p>Content</p>
    </div>
    <div id="structure" class="section scrollspy">
      <p>Content</p>
    </div>
  </div>
  <div class="hide-on-small-only m3">
    <nav aria-label="On this page">
      <ul class="section table-of-contents">
        <li><a href="#introduction">Introduction</a></li>
        <li><a href="#structure">Structure</a></li>
      </ul>
    </nav>
  </div>
</div>

Initialization

The IIFE bundle exposes Expressive.ScrollSpy. Call init yourself when you need options other than the defaults, or let Expressive.AutoInit() start every .scrollspy.

document.addEventListener('DOMContentLoaded', function() {
  const elems = document.querySelectorAll('.scrollspy');
  const instances = Expressive.ScrollSpy.init(elems, {
    // specify options here
  });
});

Per-instance options can also be passed through AutoInit:

Expressive.AutoInit(document.body, {
  ScrollSpy: { scrollOffset: 200 }
});

The documentation TOC is position: sticky. Scrollspy only updates which link is active.

Options

Name Type Default Description
scrollOffset Number 200 Offset in pixels. Written as --md-comp-scrollspy-offset (section scroll-margin) and used as the observer rootMargin.
activeClass String 'active' Class applied to the active table-of-contents link.
getActiveElement Function see below Optional. Returns a CSS selector for the element that should receive activeClass, given the section’s id. The default finds a[href="#id"] by comparing attributes — it does not interpolate the id into a selector.
keepTopElementActive Boolean false If true, keep the last section above the viewport active when nothing intersects. If there is no such section, the first one stays active.
getActiveElement

Only needed when the TOC is not a[href="#id"]. Return a CSS selector. Escape any author-controlled id yourself.

function(id) {
  return '[data-section="' + CSS.escape(id) + '"]';
}

Methods

All methods are called on the plugin instance. You can get the instance like this:
const instance = Expressive.ScrollSpy.getInstance(elem);
.destroy();

Unobserve the section and tear down the shared observer when it is the last spy.

instance.destroy();

Properties

Name Type Description
el Element The DOM element the plugin was initialized with (the .scrollspy section).
options Object The options the instance was initialized with.
  • Source color

    The seed every generated ramp derives from. Pick one and browse the docs — the whole theme follows. Error does not: it is a fixed hue.