{"id":29638128,"url":"https://github.com/fsegurai/scrollspy","last_synced_at":"2026-04-18T22:06:19.366Z","repository":{"id":303858580,"uuid":"1017015410","full_name":"fsegurai/scrollspy","owner":"fsegurai","description":"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.","archived":false,"fork":false,"pushed_at":"2025-07-10T03:02:08.000Z","size":343,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-07-10T04:46:44.526Z","etag":null,"topics":["accessibility","javascript","library","lightweight","scroll","scrollspy","tableofcontents","toc","typescript"],"latest_commit_sha":null,"homepage":"https://fsegurai.github.io/scrollspy/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fsegurai.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-07-09T22:27:07.000Z","updated_at":"2025-07-10T03:02:12.000Z","dependencies_parsed_at":"2025-07-10T04:49:46.314Z","dependency_job_id":"e37354ef-3efc-4f36-b9c6-e8b1e9e1f84e","html_url":"https://github.com/fsegurai/scrollspy","commit_stats":null,"previous_names":["fsegurai/scrollspy"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/fsegurai/scrollspy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fsegurai%2Fscrollspy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fsegurai%2Fscrollspy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fsegurai%2Fscrollspy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fsegurai%2Fscrollspy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fsegurai","download_url":"https://codeload.github.com/fsegurai/scrollspy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fsegurai%2Fscrollspy/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266360009,"owners_count":23917354,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-21T11:47:31.412Z","response_time":64,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["accessibility","javascript","library","lightweight","scroll","scrollspy","tableofcontents","toc","typescript"],"created_at":"2025-07-21T19:06:18.224Z","updated_at":"2026-04-18T22:06:19.357Z","avatar_url":"https://github.com/fsegurai.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\" class=\"intro\"\u003e\n  \u003cimg alt=\"ScrollSpy Logo\" src=\"https://raw.githubusercontent.com/fsegurai/scrollspy/main/demo/public/scrollspy.svg\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\" class=\"intro\"\u003e\n  \u003ca href=\"https://github.com/fsegurai/scrollspy\"\u003e\n      \u003cimg src=\"https://img.shields.io/azure-devops/build/fsegurai/93779823-473d-4fb3-a5b1-27aaa1a88ea2/26/main?label=Build%20Status\u0026\"\n          alt=\"Build Main Status\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/fsegurai/scrollspy/releases/latest\"\u003e\n      \u003cimg src=\"https://img.shields.io/github/v/release/fsegurai/scrollspy\"\n          alt=\"Latest Release\"\u003e\n  \u003c/a\u003e\n  \u003cbr\u003e\n  \u003cimg alt=\"GitHub contributors\" src=\"https://img.shields.io/github/contributors/fsegurai/scrollspy\"\u003e\n  \u003cimg alt=\"Dependency status for repo\" src=\"https://img.shields.io/librariesio/github/fsegurai/scrollspy\"\u003e\n  \u003ca href=\"https://opensource.org/licenses/MIT\"\u003e\n    \u003cimg alt=\"GitHub License\" src=\"https://img.shields.io/github/license/fsegurai/scrollspy\"\u003e\n  \u003c/a\u003e\n  \u003cbr\u003e\n  \u003cimg alt=\"Stars\" src=\"https://img.shields.io/github/stars/fsegurai/scrollspy?style=square\u0026labelColor=343b41\"/\u003e \n  \u003cimg alt=\"Forks\" src=\"https://img.shields.io/github/forks/fsegurai/scrollspy?style=square\u0026labelColor=343b41\"/\u003e\n\u003c/p\u003e\n\n**A library for scrollspy functionality**\n\n`@fsegurai/scrollspy` is a dependency-free, lightweight scrollspy library that highlights navigation links based on\nscroll position. Perfect for documentation sites, blogs, and landing pages with sticky tables of contents.\n\n---\n\n## 📋 Table of Contents\n\n- [🚀 Features](#-features)\n- [📦 Installation](#-installation)\n    - [NPM](#npm)\n    - [CDN / HTML](#cdn--html)\n- [🧠 Usage](#-usage)\n    - [HTML Example](#html-example)\n    - [JavaScript Example](#javascript-example)\n    - [TypeScript Example](#typescript-example)\n- [🧪 Demo Integration](#-demo-integration)\n- [⚙️ Options](#️-options)\n- [📡 Events](#-events)\n    - [`gumshoeactivate`](#gumshoeactivate)\n    - [`gumshoedeactivate`](#gumshoedeactivate)\n    - [Type-Safe Event Listeners](#type-safe-event-listeners)\n- [🔁 Dynamic Content Support](#-dynamic-content-support)\n- [📘 API](#-api)\n- [🎯 TypeScript Support](#-typescript-support)\n- [✅ Browser Support](#-browser-support)\n- [🧼 License](#-license)\n\n---\n\n## 🚀 Features\n\n- ⚡️ Lightweight (no dependencies)\n- 📘 **100% TypeScript** with full type definitions\n- 🔍 Intelligent scroll-based section detection\n- 🧩 Nested navigation support\n- 🧭 Works with dynamic or static content\n- 🎯 Scroll offset for fixed headers\n- 🔄 Automatic DOM mutation observer (optional)\n- 🎉 Type-safe custom activation events\n- 🧼 Clean API with init/refresh/destroy\n\n---\n\n## 📦 Installation\n\n### NPM\n\n```bash\nnpm install @fsegurai/scrollspy\n```\n\n### CDN / HTML\n\n```html\n\n\u003cscript type=\"module\"\u003e\n    import ScrollSpy from '@fsegurai/scrollspy';\n\n    const spy = new ScrollSpy('#toc');\n\u003c/script\u003e\n```\n\n---\n\n## 🧠 Usage\n\n### HTML Example\n\n```html\n\n\u003cnav id=\"toc\"\u003e\n    \u003cul\u003e\n        \u003cli\u003e\u003ca href=\"#intro\"\u003eIntro\u003c/a\u003e\u003c/li\u003e\n        \u003cli\u003e\u003ca href=\"#install\"\u003eInstall\u003c/a\u003e\u003c/li\u003e\n        \u003cli\u003e\n            \u003ca href=\"#usage\"\u003eUsage\u003c/a\u003e\n            \u003cul\u003e\n                \u003cli\u003e\u003ca href=\"#basic\"\u003eBasic\u003c/a\u003e\u003c/li\u003e\n                \u003cli\u003e\u003ca href=\"#advanced\"\u003eAdvanced\u003c/a\u003e\u003c/li\u003e\n            \u003c/ul\u003e\n        \u003c/li\u003e\n    \u003c/ul\u003e\n\u003c/nav\u003e\n\n\u003cmain\u003e\n    \u003ch2 id=\"intro\"\u003eIntro\u003c/h2\u003e\n    \u003cp\u003e...\u003c/p\u003e\n    \u003ch2 id=\"install\"\u003eInstall\u003c/h2\u003e\n    \u003cp\u003e...\u003c/p\u003e\n    \u003ch2 id=\"usage\"\u003eUsage\u003c/h2\u003e\n    \u003ch3 id=\"basic\"\u003eBasic\u003c/h3\u003e\n    \u003cp\u003e...\u003c/p\u003e\n    \u003ch3 id=\"advanced\"\u003eAdvanced\u003c/h3\u003e\n    \u003cp\u003e...\u003c/p\u003e\n\u003c/main\u003e\n```\n\n### JavaScript Example\n\n```js\nimport ScrollSpy from '@fsegurai/scrollspy';\n\nconst spy = new ScrollSpy('#toc', {\n    offset: 80,\n    nested: true,\n    nestedClass: 'parent-active',\n    reflow: true,\n    events: true,\n    observe: true\n});\n\n// Listen for activation events\ndocument.addEventListener('gumshoeactivate', (event) =\u003e {\n    console.log('Activated:', event.detail.target.id);\n});\n```\n\n### TypeScript Example\n\n```ts\nimport ScrollSpy, {type ScrollSpyEvent, type ScrollSpyOptions} from '@fsegurai/scrollspy';\n\nconst options: ScrollSpyOptions = {\n    offset: 80,\n    nested: true,\n    nestedClass: 'parent-active',\n    reflow: true,\n    events: true,\n    observe: true\n};\n\nconst spy = new ScrollSpy('#toc', options);\n\n// Fully typed event listener\ndocument.addEventListener('gumshoeactivate', (event: Event) =\u003e {\n    const customEvent = event as CustomEvent\u003cScrollSpyEvent\u003e;\n    console.log('Activated:', customEvent.detail.target.id);\n    console.log('Nav item:', customEvent.detail.nav);\n});\n```\n\n---\n\n## 🧪 Demo Integration\n\nThe demo in `demo/scripts/utils/toc.ts` builds a nested table of contents from headings, marks each heading with\n`data-gumshoe`, and then initializes ScrollSpy against `#tableOfContents`.\n\n```ts\nimport {\n    generateTOC,\n    initScrollspy,\n    setupMobileToggle,\n    setupSmoothScroll,\n} from './utils/toc';\n\nconst content = document.querySelector('#content') as HTMLElement;\n\ngenerateTOC(content);\nsetupMobileToggle();\nsetupSmoothScroll();\ninitScrollspy();\n```\n\nIn that demo flow, the generated headings look like this:\n\n```html\n\u003ch2 id=\"intro\" data-gumshoe\u003eIntro\u003c/h2\u003e\n```\n\n`initScrollspy()` configures the instance with `content: '[data-gumshoe]'`, `offset: 120`, `bottomThreshold: 10`,\n`reflow: true`, and `events: true`.\n\n---\n\n## ⚙️ Options\n\nAll available options for customizing behavior:\n\n| Option              | Type                                          | Default           | Description                                                                                                                                                                    |\n|---------------------|-----------------------------------------------|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `nav`               | `string`                                      | —                 | **(Required)** Selector for the navigation container. This is the element ScrollSpy scans for links.                                                                           |\n| `content`           | `string`                                      | `[data-gumshoe]`  | Default selector used by the demo and dynamic-content workflows. The core lookup still resolves targets from nav fragments and `fragmentAttribute`.                            |\n| `nested`            | `boolean`                                     | `false`           | Adds a class to parent `\u003cli\u003e` items in nested TOC structures.                                                                                                                  |\n| `nestedClass`       | `string`                                      | `'active-parent'` | Class name for parent `\u003cli\u003e` elements when `nested` is `true`.                                                                                                                 |\n| `offset`            | `number \\| () =\u003e number`                      | `0`               | Scroll offset in pixels or a function returning an offset, useful for fixed headers.                                                                                           |\n| `bottomThreshold`   | `number`                                      | `100`             | Distance in pixels from the bottom of the page where the last section is auto-activated.                                                                                       |\n| `reflow`            | `boolean`                                     | `false`           | If `true`, ScrollSpy also re-detects on window resize.                                                                                                                         |\n| `events`            | `boolean`                                     | `true`            | Emits `gumshoeactivate` when the active section changes. `gumshoedeactivate` is part of the typings, but the current runtime does not dispatch it.                             |\n| `observe`           | `boolean`                                     | `false`           | Enables a `MutationObserver` that calls `refresh()` when observed DOM nodes change.                                                                                            |\n| `fragmentAttribute` | `string \\| (item: Element) =\u003e string \\| null` | `null`            | Attribute or function used to map nav items to content sections instead of relying on `href`. Supports full URLs like `/route#fragment` through the default hash parsing path. |\n| `navItemSelector`   | `string`                                      | `'a[href*=\"#\"]'`  | Selector for nav items (anchors or other elements) that should be considered by ScrollSpy.                                                                                     |\n\n\u003e If you're using `observe: true`, make sure your headings or section wrappers have a consistent structure. The\n\u003e `data-gumshoe` attribute is used by the demo and matches the default `content` selector, but section matching still\n\u003e starts from the nav fragments themselves.\n\n---\n\n### Advanced Fragment Mapping (SPA/Angular)\n\nIf you need to support full URLs in `href` (e.g. `/route#fragment`) or use a custom attribute (e.g.\n`data-scrollspy-fragment`), use the `fragmentAttribute` option:\n\n```js\n// Use a custom attribute\nconst spy = new ScrollSpy('#toc', {\n    fragmentAttribute: 'data-scrollspy-fragment',\n});\n\n// Or use a function for advanced mapping\nconst spy = new ScrollSpy('#toc', {\n    fragmentAttribute: (item) =\u003e item.getAttribute('data-scrollspy-fragment') || null,\n});\n```\n\n- The library will now match anchors using the custom attribute or function, not just `href`.\n- This is useful for Angular/SPA scenarios where you want the user to see the full URL in the browser, but scrollspy to\n  map by fragment only.\n\n---\n\n## 📡 Events\n\nThese custom events are available on `document` when ScrollSpy updates the active section.\n\n### `gumshoeactivate`\n\nTriggered when a new section becomes active.\n\n```js\ndocument.addEventListener('gumshoeactivate', (e) =\u003e {\n    console.log('Activated:', e.detail.target.id);\n    console.log('Content:', e.detail.content);\n    console.log('Nav item:', e.detail.nav);\n});\n```\n\n### About `gumshoedeactivate`\n\n`gumshoedeactivate` is included in the type definitions, but the current implementation does not dispatch it. If you\nneed deactivation hooks, listen for `gumshoeactivate` and compare the previous active section yourself.\n\nEvent `detail` includes:\n\n- `target`: The content section element\n- `content`: Alias of `target`\n- `nav`: Corresponding anchor tag from the TOC\n\n### Type-Safe Event Listeners\n\nThe library includes full TypeScript type definitions for the custom events that ship with the package. The\n`DocumentEventMap` is augmented to include both `gumshoeactivate` and `gumshoedeactivate`, even though only\n`gumshoeactivate` is emitted by the current runtime:\n\n```ts\nimport type {ScrollSpyEvent} from '@fsegurai/scrollspy';\n\n// TypeScript knows about these custom events automatically\ndocument.addEventListener('gumshoeactivate', (event: Event) =\u003e {\n    const customEvent = event as CustomEvent\u003cScrollSpyEvent\u003e;\n    // Full intellisense support for event.detail.target, content, nav\n    console.log(customEvent.detail.target.id);\n});\n```\n\n- `target`: The content section element\n- `content`: Alias of `target`\n- `nav`: Corresponding anchor tag from the TOC\n\n---\n\n## 🔁 Dynamic Content Support\n\nIf you update the TOC or headings dynamically, call:\n\n```js\nspy.refresh();\n```\n\nOr initialize with `observe: true` to let ScrollSpy refresh itself using a `MutationObserver`.\n\n---\n\n## 📘 API\n\n| Method          | Description                                                                 |\n|-----------------|-----------------------------------------------------------------------------|\n| `init()`        | Performs the initial DOM lookup, content mapping, detection, and listeners. |\n| `getContents()` | Rebuilds the internal nav-to-target map from the current DOM.               |\n| `getNavItem()`  | Resolves the nav element associated with a content section.                 |\n| `detect()`      | Re-runs detection logic based on current scroll position.                   |\n| `setup()`       | Rebuilds contents and runs detection again.                                 |\n| `refresh()`     | Same rebuild/detect cycle as `setup()`; use this after dynamic updates.     |\n| `destroy()`     | Removes listeners, clears active classes, and disconnects the observer.     |\n\n\u003e `setup()` and `refresh()` currently perform the same rebuild/detect pass.\n\n---\n\n## 🎯 TypeScript Support\n\nThe library is built entirely in **TypeScript** and exports complete type definitions:\n\n```ts\nimport ScrollSpy, {\n    type ScrollSpyOptions,\n    type ScrollSpyEvent,\n    type ContentPosition\n} from '@fsegurai/scrollspy';\n\n// Full type safety for all options\nconst options: ScrollSpyOptions = {\n    offset: 80,\n    nested: true,\n};\n\n// Constructor is fully typed\nconst spy = new ScrollSpy('#toc', options);\n\n// Event detail is typed\ndocument.addEventListener('gumshoeactivate', (event: Event) =\u003e {\n    const e = event as CustomEvent\u003cScrollSpyEvent\u003e;\n    const target: Element = e.detail.target;\n    const nav: Element = e.detail.nav;\n});\n```\n\n`ScrollSpyOptions` includes the navigation selector, offset controls, nested-navigation classes, fragment mapping, and\nthe optional MutationObserver toggle. `ScrollSpyEvent` is the shared detail payload for the activation event.\n\n---\n\n## ✅ Browser Support\n\n| Browser | Support |\n|---------|---------|\n| Chrome  | ✅       |\n| Firefox | ✅       |\n| Safari  | ✅       |\n| Edge    | ✅       |\n| IE11    | ❌       |\n\n⚠️ Requires `CustomEvent` support. You may need polyfills for legacy environments.\n\n---\n\n## 🧼 License\n\nLicensed under [MIT](https://opensource.org/licenses/MIT).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffsegurai%2Fscrollspy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffsegurai%2Fscrollspy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffsegurai%2Fscrollspy/lists"}