Scroll animations with plain CSS and a few lines of JavaScript
How to fade, slide or zoom elements in as they scroll into view without an animation library, and how to respect visitors who prefer less motion.
Quick answer: hide the element with a class, watch it with an IntersectionObserver, and add a second class when it enters the screen so a CSS transition plays. No library needed, and it stays fast if you animate only opacity and transform.
The pattern
.reveal { opacity: 0; transform: translateY(24px);
transition: opacity .6s ease, transform .6s ease; }
.reveal.is-in { opacity: 1; transform: none; }
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting) { e.target.classList.add('is-in'); io.unobserve(e.target); }
});
}, { threshold: 0.15 });
document.querySelectorAll('.reveal').forEach((el) => io.observe(el));
The observer fires once when the element is 15% visible, adds the class and stops watching it, so there is nothing running afterwards.
Keep it smooth
- Animate opacity and transform. They run on the compositor and do not force layout. Animating
top,heightor margins makes the page jank. - Keep distances small. Around 20 to 40 pixels is enough for a slide.
- Stagger carefully. A small delay between items looks nice. Long chains make people wait for content.
Respect reduced motion
Some visitors turn on a system setting because motion makes them ill. Honour it:
@media (prefers-reduced-motion: reduce) {
.reveal { opacity: 1; transform: none; transition: none; }
}
Do not hide content without a fallback
If the script fails, elements marked .reveal stay invisible. Add the hidden style only when JavaScript is running, for example by setting a class on html first.
Let a tool write it
The animate-on-scroll generator lets you pick an effect, set duration, delay and easing, preview it and copy the CSS and script. The easing editor helps you tune the curve, and the effects library lets you browse 20 ready-made animations.