Skip to content

Scrollspy is a lightweight JS library that highlights active navigation links based on scroll position. Supports nested menus and custom events for seamless single-page navigation.

License

Notifications You must be signed in to change notification settings

fsegurai/scrollspy

Repository files navigation

ScrollSpy Logo

Build Main Status Latest Release
GitHub contributors Dependency status for repo GitHub License
Stars Forks

A library for scrollspy functionality

@fsegurai/scrollspy is a dependency-free, lightweight scrollspy library that highlights navigation links based on scroll position. Perfect for documentation sites, blogs, and landing pages with sticky tables of contents.

πŸ“‹ Table of Contents


πŸš€ Features

  • ⚑️ Lightweight (no dependencies)
  • πŸ” Intelligent scroll-based section detection
  • 🧩 Nested navigation support
  • 🧭 Works with dynamic or static content
  • 🎯 Scroll offset for fixed headers
  • πŸ”„ Automatic DOM mutation observer (optional)
  • πŸŽ‰ Emits custom activation events
  • 🧼 Clean API with setup/destroy

πŸ“¦ Installation

NPM

npm install @fsegurai/scrollspy

CDN / HTML

<script type="module" src="path/to/scrollspy.esm.js"></script>

🧠 Usage

HTML Example

<nav id="toc">
    <ul>
        <li><a href="#intro">Intro</a></li>
        <li><a href="#install">Install</a></li>
        <li>
            <a href="#usage">Usage</a>
            <ul>
                <li><a href="#basic">Basic</a></li>
                <li><a href="#advanced">Advanced</a></li>
            </ul>
        </li>
    </ul>
</nav>

<main>
    <h2 id="intro">Intro</h2>
    <p>...</p>
    <h2 id="install">Install</h2>
    <p>...</p>
    <h2 id="usage">Usage</h2>
    <h3 id="basic">Basic</h3>
    <p>...</p>
    <h3 id="advanced">Advanced</h3>
    <p>...</p>
</main>

JavaScript/TypeScript Example

import scrollspy from '@fsegurai/scrollspy';

const spy = new scrollspy('#toc', {
    offset: 80,
    nested: true,
    nestedClass: 'parent-active',
    reflow: true,
    events: true,
    observe: true
});

βš™οΈ Options

All available options for customizing behavior:

Option Type Default Description
nav string β€” (Required) Selector for the navigation container. Specifies where to find the navigation links (usually your Table of Contents or sidebar).
content string [data-gumshoe] Selector for scrollable content sections (matched by ID from nav href). Used for observing DOM mutations (if observe: true) and for internal mapping.
You don’t usually need to change this unless your content isn’t identified by IDs directly
nested boolean false Add a class to parent <li> items in nested TOC structures
nestedClass string 'active-parent' Class name for parent <li> elements when nested is true
offset number | () => number 0 Scroll offset in pixels or a function returning an offset (e.g. fixed headers)
bottomThreshold number 100 Distance (in px) from bottom of page where last section is auto-activated.
reflow boolean false If true, recomputes layout on window resize
events boolean true Emits custom DOM events (gumshoeactivate, gumshoedeactivate)
observe boolean false Enables a MutationObserver to track DOM changes and refresh content
fragmentAttribute string | (item: Element) => string | null null Attribute or function used to map nav items to content sections, instead of relying on the href attribute.
navItemSelector string 'a[href*="#"]' Selector for nav items (anchors). Use to further filter which anchors are considered navigation items.

If you're using observe: true, make sure your headings or section wrappers have a consistent structure, and set a data-gumshoe attribute if you change the default selector.


Advanced Fragment Mapping (SPA/Angular)

If you need to support full URLs in href (e.g. /route#fragment) or use a custom attribute (e.g. data-scrollspy-fragment), use the fragmentAttribute option:

// Use a custom attribute
const spy = new scrollspy('#toc', {
  fragmentAttribute: 'data-scrollspy-fragment',
});

// Or use a function for advanced mapping
const spy = new scrollspy('#toc', {
  fragmentAttribute: (item) => item.getAttribute('data-scrollspy-fragment') || null,
});
  • The library will now match anchors using the custom attribute or function, not just href.
  • This is useful for Angular/SPA scenarios where you want the user to see the full URL in the browser, but scrollspy to map by fragment only.

πŸ“‘ Events

These custom events are fired on the document when scrollspy updates:

gumshoeactivate

Triggered when a new section becomes active.

document.addEventListener('gumshoeactivate', (e) => {
    console.log('Activated:', e.detail.target.id);
});

gumshoedeactivate

Triggered when a section is deactivated (not currently visible).

document.addEventListener('gumshoedeactivate', (e) => {
    console.log('Deactivated:', e.detail.target.id);
});

Event detail includes:

  • target: The content section element
  • content: Alias of target
  • nav: Corresponding anchor tag from the TOC

πŸ” Dynamic Content Support

If you update the TOC or headings dynamically, call:

spy.refresh();

Or initialize with observe: true to let it auto-refresh using a MutationObserver.


πŸ“˜ API

Method Description
setup() Manually sets up the scrollspy instance again from scratch
detect() Re-runs detection logic based on current scroll position
refresh() Rebuilds internal nav/content map (used after dynamic updates)
destroy() Tears down scrollspy instance, removes listeners and DOM references

βœ… Browser Support

Browser Support
Chrome βœ…
Firefox βœ…
Safari βœ…
Edge βœ…
IE11 ❌

⚠️ Requires IntersectionObserver and CustomEvent. You may need polyfills for legacy environments.


🧼 License

Licensed under MIT.

About

Scrollspy is a lightweight JS library that highlights active navigation links based on scroll position. Supports nested menus and custom events for seamless single-page navigation.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published