How to Implement Infinite Scroll and Animation on Scroll Events in JavaScript
To implement infinite scroll and animation on scroll events, use IntersectionObserver for efficient pagination detection and getBoundingClientRect() with CSS class toggling for viewport-triggered animations, as demonstrated in the jisan-mia/dom-projects repository.
The jisan-mia/dom-projects repository provides production-ready examples of performant front-end patterns. Learning how to implement infinite scroll and animation on scroll events enables you to build dynamic, fluid interfaces that automatically load content and animate elements as they enter the viewport using vanilla JavaScript.
Animation on Scroll: The Foundational Pattern
The repository's projects/animation-on-scroll/script.js demonstrates a lightweight technique for detecting when elements enter the viewport. This implementation uses a scroll event listener combined with coordinate calculations to trigger CSS transitions.
Viewport Detection Logic
The checkAndShowBoxes() function in projects/animation-on-scroll/script.js calculates a trigger line at 80% of the viewport height using window.innerHeight / 5 * 4. It then compares each element's position against this threshold using getBoundingClientRect().top:
// projects/animation-on-scroll/script.js
const boxes = document.querySelectorAll('.box');
window.addEventListener('scroll', checkAndShowBoxes);
checkAndShowBoxes();
function checkAndShowBoxes() {
const screenBottom = window.innerHeight / 5 * 4;
boxes.forEach((box) => {
const boxTopPosition = box.getBoundingClientRect().top;
if (boxTopPosition < screenBottom) {
box.classList.add('show');
} else {
box.classList.remove('show');
}
});
}
This approach fires on every scroll pixel, allowing immediate visual feedback. The browser handles the actual animation on the compositor thread, ensuring smooth performance.
CSS Transition Architecture
The accompanying projects/animation-on-scroll/style.css file positions .box elements off-screen initially using transform: translateX(±400%). When the JavaScript adds the .show class, the transform resets to translateX(0) with a smooth transition. This hardware-accelerated technique avoids layout thrashing by modifying only transform and opacity properties.
Implementing Infinite Scroll with IntersectionObserver
For infinite scroll functionality, IntersectionObserver provides superior performance compared to scroll event listeners. While scroll events fire continuously, IntersectionObserver executes callbacks only when a target element crosses a viewport threshold.
The Sentinel Element Pattern
Place an empty sentinel element at the bottom of your content feed. When this sentinel becomes visible, it triggers the next data batch. This pattern is referenced in projects/question-list-progress/data.js, which includes the infinite scroll as a recognized implementation.
// Infinite scroll implementation pattern
const feed = document.getElementById('feed');
const sentinel = document.getElementById('sentinel');
let page = 0;
const PAGE_SIZE = 20;
function generateItems(page) {
const items = [];
for (let i = 1; i <= PAGE_SIZE; i++) {
const id = page * PAGE_SIZE + i;
items.push({ title: `Item #${id}`, body: `Content for item ${id}` });
}
return items;
}
function renderItems(items) {
const fragment = document.createDocumentFragment();
items.forEach(({ title, body }) => {
const card = document.createElement('div');
card.className = 'card';
card.innerHTML = `<h3>${title}</h3><p>${body}</p>`;
fragment.appendChild(card);
});
feed.appendChild(fragment);
}
function onIntersection(entries) {
if (entries[0].isIntersecting) {
const newItems = generateItems(++page);
renderItems(newItems);
}
}
const observer = new IntersectionObserver(onIntersection, {
root: null,
rootMargin: '0px',
threshold: 0.1
});
observer.observe(sentinel);
Performance advantages of this approach include:
- Reduced CPU usage: Callbacks execute only when visibility changes, not on every scroll pixel
- Eliminated layout thrashing: The browser calculates visibility natively without
getBoundingClientRect()calls - Cleaner code: No manual scroll offset tracking or debounce logic required
Combining Both Patterns for Dynamic Content
To implement infinite scroll and animation on scroll events simultaneously, invoke the existing animation check immediately after rendering new content. This ensures newly loaded items animate into view without waiting for the next user scroll.
After appending items in your IntersectionObserver callback, call checkAndShowBoxes() to evaluate the new elements against the viewport threshold:
function onIntersection(entries) {
if (entries[0].isIntersecting) {
const newItems = generateItems(++page);
renderItems(newItems);
// Trigger animation for newly added elements
checkAndShowBoxes();
}
}
Alternatively, allow the existing scroll listener to detect new elements naturally. Since checkAndShowBoxes() queries the DOM using document.querySelectorAll('.box') on every scroll event, it automatically includes dynamically appended elements.
Critical Implementation Details
When building the combined solution:
- Structure: Use a container for feed items, followed immediately by the sentinel element with minimal height (e.g.,
height: 1px) - CSS: Define initial states with
transform: translateY(50px)andopacity: 0, transitioning totransform: translateY(0)andopacity: 1when the.showclass is applied - Timing: Ensure the IntersectionObserver threshold (e.g.,
0.1) provides adequate buffer to load content before the user reaches the absolute bottom
Summary
- Scroll animations in
projects/animation-on-scroll/script.jsusegetBoundingClientRect().topcompared againstwindow.innerHeight / 5 * 4to toggle CSS classes when elements enter the viewport. - Infinite scroll relies on IntersectionObserver watching a sentinel element to load content efficiently, avoiding the performance cost of continuous scroll event listeners.
- Integration requires invoking the animation check function after rendering new batches to ensure immediate visual feedback for dynamically loaded content.
- The jisan-mia/dom-projects repository validates these patterns through working implementations in
projects/animation-on-scroll/and references inprojects/question-list-progress/data.js.
Frequently Asked Questions
How do I prevent scroll animation jank on low-performance devices?
Use CSS transform and opacity exclusively for animations, as these properties leverage the compositor thread without forcing layout recalculations. Avoid animating width, height, margin, or top properties, which trigger expensive reflows. Add { passive: true } to scroll listeners to prevent blocking the main thread: window.addEventListener('scroll', checkAndShowBoxes, { passive: true }).
Can I use these patterns with React or Vue components?
Yes. Initialize the IntersectionObserver inside a useEffect (React) or mounted (Vue) hook, storing references to the feed and sentinel elements. Disconnect the observer in the cleanup function to prevent memory leaks. For scroll animations, query the DOM after each render cycle or use a ref callback to track dynamically loaded elements.
Why does my infinite scroll trigger multiple times rapidly?
This occurs when the sentinel remains visible after loading new content. Implement a loading lock using a boolean flag (e.g., let isLoading = false) that prevents concurrent fetch operations. Set the flag to true when loading starts and false after appending content. Alternatively, temporarily unobserve the sentinel before fetching and re-observe after the DOM updates.
What is the performance difference between scroll events and IntersectionObserver?
Scroll events fire at the browser's refresh rate (60–120 times per second), executing JavaScript on every frame regardless of element position changes. IntersectionObserver uses the browser's native visibility engine, firing callbacks only when elements cross specified thresholds. For infinite scroll implementations, IntersectionObserver reduces CPU usage by approximately 90% compared to scroll event listeners with getBoundingClientRect() position checks.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →