Scrolling on the web should be smooth and responsive. Executing heavy tasks—such as layout recalculations, analytics data beacons tracking, or dynamic DOM updates—during scrolling can saturate the main thread, resulting in dropped frames and layout thrashing.
Historically, developers have relied on debouncing the scroll event using setTimeout() to guess when a scroll is finished. However, these debounced functions are notoriously unreliable. They may trigger while the user’s is still scrolling.
The scrollend event offers a highly reliable, performance-driven solution. The browser fires a scrollend event exactly when a scroll has rested, all transitions are finished, and a touch gesture has been released.
How to Implement
To implement a defer-work pattern:
- Set Up a Scrollable Container: Create a container with
overflow: autooroverflow: scroll. - Listen for
scrollevents: Only use this listener for basic dynamic metrics or informative layout styling. Do not execute heavy work here. - Listen for
scrollendevents: Register ascrollendcallback on the scroll container or the document itself. - Execute Expensive work in the callback: This is where it's safe to fetch dynamic content or trigger comprehensive DOM layouts.
Example Code
.scroll-container {
height: 300px;
overflow-y: auto;
}
const scroller = document.querySelector('.scroll-container');
// 1. Informative feedback during scroll
scroller.addEventListener('scroll', () => {
// Avoid dynamic heavier data updates here
console.log('Scrolling dynamically... updates deferred');
});
// 2. Safe callback when scrolling rests
scroller.addEventListener('scrollend', () => {
// Run layout recalculations or analytical beacons updates here
const currentVisibleSection = findMostVisibleSection(scroller);
fetchAdditionalData(currentVisibleSection);
});
Strategic Implementation & Best Practices
- DO use
scrollendinstead of debouncedscrollevents when firing layout data beacons or fetching new content content layout dynamically. - DO consider pairing this with
scrollSnapChangeorscrollSnapChangingsnap interactions if you're building carousels or testimonial galleries slides. - DO NOT bundle layout-dependent dynamic updates inside dynamic visual scroll callbacks.
- DO consider that visual viewport zooming and scrolling triggers the
scrollendevent correctly.
Fallback Strategy
For unsupported browsers, fall back to a debounced scroll event with setTimeout to dispatch a custom scrollend event.
function initializeDemo() {
const scroller = document.querySelector('#scroller');
scroller.addEventListener('scrollend', () => {
// Safe execution
});
}
if ('onscrollend' in window) {
initializeDemo();
} else {
initializeDemo();
const scroller = document.querySelector('#scroller');
scroller.addEventListener('scroll', () => {
clearTimeout(window.scrollendtimer);
window.scrollendtimer = setTimeout(() => {
scroller.dispatchEvent(new CustomEvent('scrollend'));
}, 100);
});
}