Lightweight scroll-trigger plugin for tracking section visibility and syncing navigation state
npm install @magic-spells/scroll-triggerLightweight scroll-spy plugin for tracking section visibility and syncing navigation state. Perfect for collection pages, documentation, and long-form content. Only 1.5kb gzipped.
- đĒļ Tiny bundle - Only 1.5kb gzipped
- đ¯ IntersectionObserver-based - Modern, performant section tracking
- đ Callback system - Easy integration with custom navigation
- ⥠Throttled updates - Optimized performance with configurable throttling
- đ Precise control - Customizable trigger offset from viewport bottom
- đ¨ Zero dependencies - Pure vanilla JavaScript
- đ§ Flexible API - Supports CSS selectors, NodeList, or element arrays
- đĻ Multiple formats - ESM, CommonJS, and UMD builds
``bash`
npm install @magic-spells/scroll-trigger
Or use via CDN:
`html`
`javascript
import ScrollTrigger from '@magic-spells/scroll-trigger';
const trigger = new ScrollTrigger({
sections: '.collection-section',
offset: 100,
onIndexChange: ({ currentIndex, currentElement }) => {
// Update your navigation
console.log('Active section:', currentIndex);
console.log('Trigger element:', currentElement);
}
});
`
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| sections | string\|NodeList\|Array | required | Sections to track (CSS selector, NodeList, or Array) |offset
| | number\|string | 100 | Distance from bottom of viewport to trigger active state (px or percentage like '50%') |threshold
| | number | 0.1 | IntersectionObserver threshold (0-1) |throttle
| | number | 100 | Throttle delay for updates (ms) |behavior
| | string | 'smooth' | Scroll behavior ('smooth' or 'auto') |onIndexChange
| | function | null | Callback when active section changes (receives object: { currentIndex, previousIndex, currentElement, previousElement }) |
Each tracked element can override the global offset configuration using the data-animate-offset attribute:
`html`Uses global offset (10%)Triggers at 20% from bottomTriggers at 50px from bottomTriggers at 15% from bottom
`javascript`
const scrollAnimation = new ScrollTrigger({
sections: '[data-animate-fade-up]',
offset: '10%', // Default offset for all elements
onIndexChange: ({ currentElement }) => {
if (currentElement && !currentElement.hasAttribute('data-animate-loaded')) {
currentElement.setAttribute('data-animate-loaded', '');
}
}
});
How it works:
- Each element is checked against its own trigger line based on its custom offset
- Elements without data-animate-offset use the global offset from config100
- Supports both pixel values () and percentages ('20%')
- Perfect for staggered animations or different timing for different elements
`javascript`
const currentIndex = trigger.getCurrentIndex();
`javascript`
const element = trigger.getCurrentElement();
`javascript`
const elements = trigger.getElements();
`javascript`
trigger.scrollToIndex(2, {
behavior: 'smooth',
offset: 20 // Additional offset in pixels (positive = section appears higher)
});
`javascript`
const element = document.querySelector('.my-section');
trigger.scrollToElement(element);
`javascript`
trigger.refresh();
`javascript`
trigger.updateConfig({
offset: 150,
throttle: 200
});
`javascript`
trigger.destroy();
The tracker emits a custom event on the window:
`javascript`
window.addEventListener('scroll-trigger:change', (e) => {
console.log('New index:', e.detail.index);
console.log('Previous index:', e.detail.previousIndex);
console.log('Current element:', e.detail.section);
console.log('Previous element:', e.detail.previousSection);
});
`html
`
You can use multiple ScrollTrigger instances to create different effects. Here's how to add scroll-triggered fade-up animations:
`html
Featured Products
Product 1
Product 2
Product 3
More Products
Product 4
Product 5
Product 6
`
Key Points:
- Elements start hidden with opacity: 0, translateY(60px), and blur(3px)data-animate-loaded
- When they enter the trigger zone, is addedhasAttribute
- CSS transitions animate them to visible state
- The check ensures animations only trigger oncedata-animate-offset
- Each element can have a custom to trigger at different positions
- You can combine multiple ScrollTrigger instances for different purposes
Note: ScrollTrigger does not automatically manage ARIA attributes. You must implement accessibility features yourself in your onIndexChange callback.
For accessible navigation that works with screen readers and keyboard navigation:
`html
Cereal
Granola
`
`javascript
const navItems = document.querySelectorAll('.nav-item');
const trigger = new ScrollTrigger({
sections: '[data-section-trigger]',
offset: '50%',
onIndexChange: ({ currentIndex }) => {
navItems.forEach((item, i) => {
if (i === currentIndex) {
item.classList.add('active');
// Use aria-current to indicate current location
item.setAttribute('aria-current', 'location');
} else {
item.classList.remove('active');
item.removeAttribute('aria-current');
}
});
}
});
// Prevent default, use smooth scroll, and update URL
navItems.forEach((item, index) => {
item.addEventListener('click', (e) => {
e.preventDefault();
trigger.scrollToIndex(index);
// Update URL for bookmarking/sharing
history.pushState(null, '', item.getAttribute('href'));
});
});
`
1. Use aria-current="location" instead of aria-selected for navigation
2. Use tags with href for keyboard navigation and right-click supportaria-label
3. Add to the
See the /demo/index.html` file for a complete accessible implementation.
- Modern browsers with IntersectionObserver support
- Chrome 51+
- Firefox 55+
- Safari 12.1+
- Edge 15+
MIT Š Cory Schulz