CSS Scroll-Driven Animations let you animate elements based on scroll position, entirely without JavaScript. With animation-timeline: scroll() and animation-timeline: view(), you can build progress bars, fade-ins, and parallax effects in pure CSS. Performant, elegant, and future-proof.

🤔 What Are Scroll-Driven Animations?

Imagine scrolling through a website and elements move, fade in, or transform, all perfectly synced to your scroll position. Sounds like a lot of JavaScript? Not anymore.

CSS Scroll-Driven Animations are a new CSS specification that lets you tie existing CSS animations to scroll progress. Instead of a time-based animation, the animation runs as fast or slow as you scroll. No requestAnimationFrame, no IntersectionObserver, no event listeners. Pure CSS.

There are two main concepts:

  • Scroll Progress Timeline, The animation is tied to the overall scroll progress of a container.
  • View Progress Timeline, The animation starts and ends when an element scrolls into the viewport.

🕰️ The Old Way: JavaScript and IntersectionObserver

Until now, the standard approach for scroll-based animations was a mix of JavaScript and CSS. You'd either use a scroll event listener or the IntersectionObserver to toggle classes.

Intersection Observer: A powerful tool for efficient web design 🚀
Learn how the Intersection Observer works and why CSS Scroll-Driven Animations can partially replace it.

A typical example looked something like this:

// The old way with IntersectionObserver
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.classList.add('visible');
    }
  });
}, { threshold: 0.1 });

document.querySelectorAll('.animate-on-scroll').forEach(el => {
  observer.observe(el);
});
/* Plus the CSS class */
.animate-on-scroll {
  opacity: 0;
  transform: translateY(30px);
  transition: opacity 0.6s, transform 0.6s;
}

.animate-on-scroll.visible {
  opacity: 1;
  transform: translateY(0);
}

This works. But it has downsides: you need JavaScript, the animation isn't truly tied to scroll position (it only triggers once), and with many elements, performance can suffer.

🚀 The New Way: Pure CSS with animation-timeline

Here's where it gets exciting. With CSS Scroll-Driven Animations, you only need CSS. The key is two new properties:

  • animation-timeline: scroll(), Ties the animation to scroll progress.
  • animation-timeline: view(), Ties the animation to an element's visibility in the viewport.

scroll(), The Scroll Progress Timeline

With scroll(), you tell the browser: "Hey, use the scroll progress as the timeline for this animation." The function accepts two optional parameters:

animation-timeline: scroll(<scroller> <axis>);

/* Examples */
animation-timeline: scroll();              /* nearest scrollable ancestor, block axis */
animation-timeline: scroll(root);          /* the root element (viewport) */
animation-timeline: scroll(nearest);       /* nearest scrollable ancestor */
animation-timeline: scroll(root inline);   /* root element, inline axis */

view(), The View Progress Timeline

view() is perfect for elements that should animate when they scroll into the visible area. Think fade-ins, slide-ins, or scale effects.

animation-timeline: view();
animation-timeline: view(block);           /* block axis (default) */
animation-timeline: view(inline);          /* inline axis */
animation-timeline: view(block 50px);      /* with inset */

🎯 Practical Example 1: Scroll Progress Bar

A classic example: a progress bar at the top of the page showing how far you've scrolled. This used to require JavaScript. Now? Three CSS rules.

.progress-bar {
  position: fixed;
  top: 0;
  left: 0;
  height: 4px;
  background: linear-gradient(to right, #6366f1, #8b5cf6);
  transform-origin: left;

  /* The magic */
  animation: grow-progress linear;
  animation-timeline: scroll(root);
}

@keyframes grow-progress {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}

That's it. No JavaScript. The bar scales automatically based on your scroll position. Smooth, performant, and declarative.

Size specifications in CSS: A developer's joys and sorrows
CSS units like vh, vw and friends also play a role in Scroll-Driven Animations.

🎯 Practical Example 2: Fade-In on Scroll

Want elements to fade in as they scroll into view? With view(), it's a breeze:

.fade-in {
  animation: fade-in-up linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 100%;
}

@keyframes fade-in-up {
  from {
    opacity: 0;
    transform: translateY(50px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

The element starts invisible and fades in as it enters the viewport. animation-range: entry 0% entry 100% means: the animation runs during the entire "entry" phase, from the first pixel until the element is fully visible.

🎯 Practical Example 3: Parallax Effect

Parallax effects used to be a JavaScript nightmare. Now you can do it in pure CSS:

.parallax-element {
  animation: parallax linear both;
  animation-timeline: view();
}

@keyframes parallax {
  from {
    transform: translateY(-50px);
  }
  to {
    transform: translateY(50px);
  }
}

The element moves slower than the rest of the page. Subtle, elegant, and without a single line of JavaScript.

🎚️ animation-range: Precise Control

The real power lies in animation-range. It lets you control exactly when the animation should start and end. There are several named ranges:

RangeDescription
entryElement enters the viewport
exitElement exits the viewport
containElement is fully visible
coverFrom first to last visible pixel
/* Only animate during entry */
animation-range: entry;

/* Only the first 50% of entry */
animation-range: entry 0% entry 50%;

/* From entry to exit */
animation-range: entry 0% exit 100%;

/* Only when fully visible */
animation-range: contain;

/* With fixed values */
animation-range: entry 20px entry 200px;

This gives you incredibly fine-grained control over your animations. For example, an element that only fades in during the first half of entry and then stays put.

⚙️ @keyframes in a Scroll Context

Important point: your @keyframes work exactly the same as always. The only difference is that the timeline isn't measured in seconds but in scroll progress (0% to 100%).

@keyframes reveal {
  0% {
    opacity: 0;
    transform: scale(0.8) rotate(-5deg);
    filter: blur(10px);
  }
  50% {
    opacity: 1;
    filter: blur(0);
  }
  100% {
    transform: scale(1) rotate(0deg);
  }
}

.reveal-element {
  animation: reveal linear both;
  animation-timeline: view();
  animation-range: entry;
}

The beauty of it: you can define complex, multi-step animations that perfectly adapt to scroll position. Everything stays declarative and predictable.

🌐 Browser Support and Progressive Enhancement

As of March 2026, browser support is looking pretty solid. Chrome and Edge have supported Scroll-Driven Animations since version 115. Firefox has had them enabled by default since version 131. Safari supports them in newer versions as well.

Still, you should use progressive enhancement. The @supports rule is your best friend:

/* Base styling without animation */
.element {
  opacity: 1;
  transform: none;
}

/* Scroll animation only when supported */
@supports (animation-timeline: view()) {
  .element {
    animation: fade-in linear both;
    animation-timeline: view();
    animation-range: entry;
  }
}

This way your site works everywhere, with animations where possible, without as a fallback. No user sees a broken page.

CSS scroll-driven animations - CSS | MDN
The official MDN documentation for CSS Scroll-Driven Animations.
Animate elements on scroll with Scroll-driven animations
Google Chrome's comprehensive guide to scroll-driven animations in CSS.

⚡ Performance: CSS vs. JavaScript

Why is the CSS approach better? Simple:

  • Main thread stays free: CSS animations run on the compositor thread. JavaScript-based animations block the main thread.
  • No layout thrashing: The browser optimizes CSS animations automatically. With JS, you have to manage that yourself.
  • Less code: No JavaScript bundle that needs to be loaded and parsed.
  • Automatic optimization: The browser can automatically throttle CSS animations when they're not visible.

In my testing, the difference on pages with many animated elements was clearly noticeable. Less jank, smoother animations, better Core Web Vitals.

💡 Conclusion

CSS Scroll-Driven Animations are a game changer. What used to require dozens of lines of JavaScript now works with a handful of CSS properties. The API is intuitive, performance is excellent, and browser support has become solid.

My tip: start with the scroll progress bar. It's the simplest example and you'll immediately see how powerful this is. Then experiment with view() and animation-range for fade-ins and reveal effects.

The future of web animations is declarative. And it's here now.