back

Scroll-driven animations without JavaScript

Scroll-linked animations used to mean IntersectionObserver, scroll event listeners, or a library like GSAP. Now CSS handles it natively with animation-timeline.

The basics

You write a normal @keyframes animation, then tell the browser to drive it with scroll progress instead of time:

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

.progress-bar {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 3px;
  background: currentColor;
  transform-origin: left;
  animation: progress linear;
  animation-timeline: scroll();
}

That's a reading progress bar. No JavaScript. As the user scrolls down the page, the bar fills from left to right. scroll() defaults to the nearest scrollable ancestor, which for a fixed element is the document.

Reveal on scroll

view() ties the animation to when an element enters and exits the viewport:

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

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

Each .card fades in as it enters the viewport. animation-range controls which portion of the element's visibility drives the animation. entry 0% is when the element first appears at the bottom edge, entry 100% is when it's fully visible.

Parallax header

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

.hero-image {
  animation: parallax linear;
  animation-timeline: scroll();
  animation-range: 0% 50%;
}

The image shifts up slower than the scroll, creating a depth effect. No scroll listeners, no requestAnimationFrame.

What this replaces

The browser handles the animation on the compositor thread, so it stays smooth even on heavy pages. No layout thrashing, no jank.

Browser support is solid in Chrome and Edge. Firefox and Safari are catching up. For non-supporting browsers, the element just appears without animation, which is a fine fallback.