@su-labs/scroll-spy
v21.0.5
Published
An Angular scroll spy utility based on IntersectionObserver for tracking active sections or menu items.
Downloads
191
Maintainers
Readme
ScrollSpy
@su-labs/scroll-spy
A lightweight Angular service and directive for managing active sections based on scroll position. Easily track which section of a page is currently visible and update your UI accordingly. Perfect for navigation menus, table of contents, or one-page scroll applications.
📚 Table of Contents
- Why Use This Library?
- Features
- Installation
- Quick Start
- Usage
- API Reference
- How It Works
- Browser Compatibility
- Related Libraries
- Troubleshooting
- Support
- Contributing
- License
💡 Why Use This Library?
- 🎯 Lightweight - Minimal bundle size
- ⚡ Reactive - Built with Angular Signals
- 🚀 Performant - Uses IntersectionObserver API
- 🎨 Flexible - Works with any layout
- 🔒 Type-Safe - Full TypeScript support
- 📦 Zero Dependencies - No bloat
- 🧪 Well Tested - Comprehensive test coverage
- 📝 Well Documented - Clear examples and API docs
Features
- Track the currently visible section on the page
- Reactive Signals for seamless integration with Angular
- Works with multiple sections
- Optional directive to automatically update the active section using IntersectionObserver
- Fully modular: use the service alone, or combine with the directive for automatic detection
- Configurable intersection thresholds
Installation
You can install this library via npm:
npm install @su-labs/scroll-spyPeer Dependencies
This library requires the following peer dependencies:
npm install @angular/common@^21 @angular/core@^21🚀 Quick Start
// 1. Install
npm install @su-labs/scroll-spy
// 2. Add directive to your sections
@for (section of sections; track section.id) {
<section [id]="section.id" [appScrollSpy]="section.id">
<h2>{{ section.name }}</h2>
</section>
}
// 3. Track active section in navigation
@for (section of sections; track section.id) {
<a
[href]="'#' + section.id"
[class.active]="activeSection() === section.id"
>
{{ section.name }}
</a>
}
// 4. Inject service in component
import { Component, inject } from '@angular/core';
import { ScrollSpyService } from '@su-labs/scroll-spy';
export class AppComponent {
scrollSpyService = inject(ScrollSpyService);
activeSection = this.scrollSpyService.activeSection;
}Usage
Using the service and directive
The most common usage is to combine the ScrollSpyService with the ScrollSpyDirective to create an automatic, reactive scroll-spy solution.
Attach the directive to your sections
In your component template, apply the appScrollSpy directive to each section you want to track. The input value should be a unique ID for that section. It's a good practice to also set the id attribute for anchor links.
@for (section of sections; track section.id) {
<a
[href]="'#' + section.id"
[class.active]="activeSection() === section.id"
>
{{ section.name }}
</a>
}
@for (section of sections; track section.id) {
<section [id]="section.id" [appScrollSpy]="section.id">
<h2>{{ section.name }}</h2>
<p>Content for the {{ section.name }} section...</p>
</section>
}Inject the service into your component
Because the ScrollSpyService is provided at the root level, you can simply inject it into any component. Directly assign the activeSection signal to a property to use it in your template.
import { Component, inject, signal } from '@angular/core';
import { ScrollSpyService, ScrollSpyDirective } from '@su-labs/scroll-spy';
import { NgClass } from '@angular/common';
@Component({
selector: 'app-scroll-example',
templateUrl: './scroll-example.component.html',
standalone: true,
imports: [ScrollSpyDirective, NgClass]
})
export class ScrollExampleComponent {
private readonly scrollSpyService = inject(ScrollSpyService);
activeSection = this.scrollSpyService.activeSection;
sections = [
{ id: 'home', name: 'Home' },
{ id: 'about', name: 'About' },
{ id: 'services', name: 'Services' },
{ id: 'contact', name: 'Contact' }
];
}📖 API Reference
Service (ScrollSpyService)
Signals
| Signal | Type | Description |
|--------|------|-------------|
| activeSection() | string \| null | ID of currently active section |
Methods
| Method | Parameters | Description |
|--------|------------|-------------|
| setActiveSection(id) | string | Manually set active section |
Directive (ScrollSpyDirective)
Inputs
| Input | Type | Description |
|-------|------|-------------|
| appScrollSpy | string | Section ID to track |
How to Use
<section [appScrollSpy]="'section-id'">
<!-- Your content -->
</section>How It Works
The ScrollSpyDirective uses the browser's native IntersectionObserver API to efficiently monitor when elements enter or exit the viewport. It's configured to trigger when a tracked section is roughly in the vertical center of the screen, providing a smooth and accurate "spy" effect.
Key Benefits:
- 🚀 Performance: No scroll event listeners needed
- 🎯 Accuracy: Precise intersection detection
- ⚡ Efficiency: Browser-native implementation
- 🔋 Battery Friendly: Optimized for mobile devices
🌐 Browser Compatibility
Works in all modern browsers that support:
- ✅ IntersectionObserver API
- ✅ Angular 21+
- ✅ ES2022+
| Browser | Version | |---------|---------| | Chrome | ≥ 58 | | Firefox | ≥ 55 | | Safari | ≥ 12.1 | | Edge | ≥ 16 |
🔗 Related Libraries
Part of the @su-labs suite:
- 👀 @su-labs/visit-tracker - User visit tracking
- 🎨 @su-labs/theme - Theme management with dark mode support
- 🔔 @su-labs/notifications - Toast notification system
- 📏 @su-labs/font-size - Accessible font size controls
🔧 Troubleshooting
Active section not updating?
Make sure you've added the appScrollSpy directive to all sections you want to track.
Multiple sections active at once?
This shouldn't happen with the default configuration. Check that section IDs are unique.
Directive not found?
Make sure you've imported ScrollSpyDirective in your component's imports array (for standalone components).
TypeScript errors?
Ensure you have Angular 21+ and TypeScript 5.7+ installed.
💬 Support
- 🐛 Found a bug? Open an issue
- 💡 Have a feature request? Start a discussion
- ⭐ Like this library? Give it a star!
Contributing
If you find any bugs or have feature requests, please open an issue or submit a pull request on our GitHub repository.
To contribute code, please ensure your changes include unit tests to maintain code quality. Please see the main repository's README.md for details on the monorepo structure.
License
This project is licensed under the MIT License. See the LICENSE file for details.
