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. |