Scroll Animations That Don't Suck
Most scroll animation is noise. Here's how to use motion to guide attention without making people dizzy — IntersectionObserver, blur reveal, and why parallax is dead.
Most scroll animation is noise
You know the site. You land. You scroll. Headlines slide in from the left. Paragraphs fade up one after another. Numbers count from zero to eighty-three. Images drift sideways as you move. By the time you reach the footer you have watched a slow-motion slideshow, and you are tired.
Motion is not the point. Attention is the point. Every animation is a thing the reader has to process before they can read the thing you actually wrote. Stack enough of them and the page becomes work. We spend a lot of our time taking animations off sites we inherit. More on what we keep in a minute.
First, the principle. Animation has a job. It is not decoration.
What motion is actually for
Motion does three things, and only three.
- It confirms an action. You tapped a button. The button pressed back. You submitted a form. It responded.
- It reveals a change in state. A drawer opened. A new view replaced the old one. Something entered the room.
- It nudges the eye toward what matters next.
Scroll-triggered animation only qualifies for the third one, and only barely. The act of scrolling is already the user telling you where they are looking. Your job is not to fight that. A subtle confirmation that a section has arrived is plenty. A six-element choreographed entrance is a performance for the designer, not the reader.
Why parallax is dead
Parallax — where background layers move at a different speed than foreground — was everywhere in 2014. It was impressive then. It is a liability now.
The problem is cost. Parallax requires the browser to repaint large areas of the screen on every single frame as you scroll. On a desktop with a discrete GPU that feels fine. On a mid-range phone it jitters. The hero image stutters. The text above it swims. The site feels broken on the exact device most of your visitors are using.
There is also an accessibility issue. Parallax and large background movement trigger motion sickness in a meaningful slice of users. It is not a small group, and it is not something you can argue around. If your site makes a percentage of visitors feel ill, you have a problem.
We stopped using parallax years ago. We have not missed it once.
The hidden cost of scroll listeners
A lot of animation tutorials tell you to attach a function to the scroll event. That event can fire sixty or more times per second while someone scrolls. If your handler does any real work — measuring elements, reading layout properties, updating styles — you will block the main thread and the page will stutter.
// The bad version — runs on every scroll frame
window.addEventListener('scroll', () => {
const rect = element.getBoundingClientRect();
if (rect.top < window.innerHeight) {
element.classList.add('visible');
}
});
getBoundingClientRect() forces the browser to calculate layout synchronously. Do that sixty times a second and the browser spends most of its time figuring out where things are instead of painting.
People try to fix this with throttling and requestAnimationFrame. That helps. But there is a better primitive.
IntersectionObserver: the right tool
IntersectionObserver is a browser API built for exactly this. You hand it a list of elements. It watches them in the background, off the main thread, and fires a callback only when one enters or leaves the viewport. No manual measurement. No frame-by-frame work.
This is the entire animation engine we ship on most sites:
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
observer.unobserve(entry.target);
}
});
}, {
threshold: 0.1,
rootMargin: '0px 0px -10% 0px',
});
document.querySelectorAll('[data-reveal]').forEach((el) => {
observer.observe(el);
});
Roughly 250 bytes gzipped. No library. No dependencies. It fires once per element, then stops watching. That is the whole trick.
The matching CSS is one rule:
[data-reveal] {
opacity: 0;
filter: blur(8px);
transform: translateY(12px);
transition: opacity .6s ease, filter .6s ease, transform .6s ease;
}
[data-reveal].is-visible {
opacity: 1;
filter: blur(0);
transform: none;
}

A blur on reveal does something pleasant. It mimics the way your eye refocuses when something comes into view. It feels organic in a way that a hard slide-up never does. We use it on images and section headers and almost nothing else.
Stagger, used sparingly
Stagger — animating a group of items one after another with a small delay — is useful in exactly one place: a grid of similar cards. Features, pricing tiers, a portfolio. The first card appears, the next follows a fraction of a second later, and the eye reads them as a set.
Two warnings.
First, cap the delay. We use 60ms between items. Anything above 100ms starts to feel like the page is loading. A stagger that takes two full seconds to complete is a stagger nobody watches.
Second, count the items. If you stagger twelve cards, the last one arrives long after the reader has moved on. Stagger three or four. For longer lists, reveal them as a group with a single transition.
Respect reduced motion
Not everyone wants movement. Some people get dizzy. Some have vestibular conditions. Some are reading on a train. The browser tells you this through a media query, and ignoring it is lazy.
@media (prefers-reduced-motion: reduce) {
[data-reveal] {
opacity: 1;
filter: none;
transform: none;
transition: none;
}
}
Two lines. The page still works. People who asked for less motion get exactly that, with no loss of content. There is no good reason to skip this.
When we break the rules
There is one place we let motion run a little longer: the hero. The first thing a visitor sees earns a second of their attention. A short staggered reveal of the headline, or a slow blur-in of the hero image, sets a tone. It tells you the site is alive before you have read a word.
Everywhere else we default to less. A small blur on section images. A subtle slide-up on headers. Nothing that competes with the content.
If you can cut an animation and the page still communicates, cut it. Your readers did not come for the animation. They came for the answer.
Want a site that moves with intent instead of by default? We build fast, hand-coded sites with motion that respects your visitors. See pricing or tell us about your project.
Need a website like this?
We build fast, SEO-optimized sites that actually rank. From $199.
Get in touch →