Why I Ditched JS Animation Libraries for CSS and IntersectionObserver
By Paul Peery · July 26, 2026 · 3 min read

Why I Dropped the JavaScript Animation Runtime
When I was reviewing performance while Upgrading EMPEERYAL to Next.js 16: What Broke and What Helped, I took a hard look at my frontend JavaScript bundle. Heavy animation runtimes like Framer Motion or GSAP add dozens of kilobytes to your download size. On a fast desktop over Wi-Fi, you might not notice. On a mid-range phone over a cellular connection, parsing and executing that script blocks the main thread.
My mobile Total Blocking Time (TBT) was taking a hit because the browser spent time computing layout transitions in JavaScript every time elements came into view. I realized 90% of what I wanted was simple: subtle fade-ins and slide-ups as the user scrolled down.
Replacing the runtime with pure CSS and a tiny native observer script cut my JavaScript payload and dropped mobile blocking time significantly.
The Strategy: CSS Keyframes + IntersectionObserver
Instead of letting JavaScript calculate frame-by-frame animation math, let the browser handle rendering natively. CSS animations run directly on the compositor thread when you stick to properties like opacity and transform.
JavaScript only has one small job: watch for when an element enters the screen and add a CSS class. Once that class is attached, CSS handles the visual work.
Here is the exact pattern so you can use it on your own site.
Step 1: Write Hardware-Accelerated CSS Keyframes
Set up your default hidden state and your visible target state in CSS. Stick strictly to transform and opacity to avoid expensive layout reflows.
/* Base state for scroll reveal elements */
.reveal {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
will-change: opacity, transform;
}
/* Class added when element is in view */
.reveal.is-visible {
opacity: 1;
transform: translateY(0);
}
The will-change property hints to the browser to create a GPU layer before the animation starts, keeping things silky smooth.
Step 2: Add a Lightweight IntersectionObserver
Old scroll handlers attach event listeners to the window scroll event, which fire hundreds of times per second and cause lag. The browser's native IntersectionObserver API runs off the main thread and notifies you only when an element enters the viewport.
Here is the script I use:
document.addEventListener("DOMContentLoaded", () => {
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add("is-visible");
obs.unobserve(entry.target); // Stop tracking once revealed
}
});
}, {
threshold: 0.1,
rootMargin: "0px 0px -40px 0px"
});
document.querySelectorAll(".reveal").forEach(el => observer.observe(el));
});
Calling obs.unobserve(entry.target) as soon as an element appears ensures the observer stops tracking it. Memory usage stays practically zero after the initial page load.
Step 3: Progressive Enhancement and Reduced Motion
A major risk with scroll animations is accidentally hiding content if JavaScript fails to execute or is turned off entirely. Content should always remain accessible.
You can protect your readers by defaulting content to visible if JS is unavailable, or by wrapping hidden styles inside progressive classes. Always respect user OS settings for motion sensitivity:
@media (prefers-reduced-motion: reduce) {
.reveal {
opacity: 1 !important;
transform: none !important;
transition: none !important;
}
}
I used a few of the AI Coding Tools I Actually Pay For in 2026 to quickly search my codebase, swap out old motion component wrappers, and replace them with standard HTML elements using these lightweight classes.
The Performance Impact
Before making this swap, mobile performance audits flagged main-thread delays whenever new sections scrolled into view.
After removing the JS animation library:
- JS Bundle Size: Down by over 30KB gzipped.
- Total Blocking Time (TBT): Slashed on mobile CPUs because layout math isn't running in JS.
- Scroll Smoothness: Zero scroll jank on slow devices.
If you only need entry fades or slide-ins, you don't need a heavy animation package. Native CSS keyframes and 15 lines of IntersectionObserver deliver a better user experience with zero runtime overhead.
Keep reading
All postsComments
No comments yet — be the first!
