Scrollspy watches a set of sections and tracks which one is in view.
The matching link gets active and aria-current="true".
The table of contents on the right of every documentation page is the live demo.
Clicking a link is a normal hash jump.
scroll-margin sets the offset and
scroll-behavior: smooth sets the motion.
- Put
scrollspyand anidon each section. - Wrap the TOC in a labelled
<nav>, because it is a set of destinations within the page. - Put
table-of-contentson its list and point each link at#that-id.
AutoInit() starts every
.scrollspy except those marked no-autoinit.
Scrollspy uses IntersectionObserver and has no
scroll listener.
Each link is at least 48dp tall, the Material touch target. For a
denser list, set --md-comp-toc-item-height on the
.table-of-contents list.
<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-compact-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
});
});
You can also pass per-instance options 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. Scrollspy writes it to
--md-comp-scrollspy-offset (section
scroll-margin) and uses it 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 and 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
Return a CSS selector. Escape any author-controlled id yourself.
Sections sharing the same callback function share one current link.
For independent tables of contents, use a separate callback for each
navigation and reuse it for all its sections. The default callback
groups all default instances together. Each section honors its own
scrollOffset and activeClass.
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();
Disconnect the section's observer, restore its original offset style, and update the current link within its group. Calling it more than once is safe.
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. |