ESC

Type to search the knowledge base.

scroll-driven Animations Intro

CSS scroll-driven animations — animation-timeline with scroll() and view(), progress-linked effects, support caveats, and reduced motion.

advanced3 min read
  • css
  • scroll-driven
  • animations

Scroll-driven animations link a CSS animation’s progress to scroll position instead of time. Parallax-style fades, progress bars, and reveal-on-enter effects can run on the compositor path without scroll event JavaScript — where supported.

Docs: MDN scroll-driven animations, animation-timeline, Scroll-driven Animations explainer.

Core idea

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

.progress {
  transform-origin: left;
  animation: grow linear both;
  animation-timeline: scroll(root block);
}

animation-timeline: scroll(...) uses a scroll timeline. Progress 0→1 maps to keyframes as the user scrolls that scroller.

scroll() vs view()

/* Scroll timeline: driven by a scroll container’s position */
.progress {
  animation-timeline: scroll(nearest block);
}

/* View timeline: driven by an element’s progress through the viewport/scrollport */
.card {
  animation: fade-up linear both;
  animation-timeline: view(block);
  animation-range: entry 0% cover 40%;
}

@keyframes fade-up {
  from {
    opacity: 0;
    transform: translateY(1rem);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
Timeline Driver
scroll() Overall scroll progress of a named/nearest/root scroller
view() Subject element’s visibility progress in a scrollport

animation-range clips which part of the timeline maps to the animation (entry, exit, cover, contain percentages).

Named timelines

.scroller {
  overflow: auto;
  scroll-timeline-name: --main;
  scroll-timeline-axis: block;
}

.scroller .bar {
  animation: grow linear both;
  animation-timeline: --main;
}

Useful when the animated element isn’t a direct child of the scroller you care about.

Progressive enhancement

Support is improving but not universal. Always provide a sensible static end state:

.card {
  opacity: 1;
  transform: none;
}

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

Reduced motion

@media (prefers-reduced-motion: reduce) {
  .card,
  .progress {
    animation: none !important;
  }
}

Scroll-linked motion can be as nauseating as time-based motion. Prefer opacity-only or no animation under reduce.

When not to use

  • Critical form UX that must not depend on scroll progress
  • Complex choreography needing WAAPI cancelation APIs across browsers without support
  • Effects that fight scrolljacking accessibility guidelines

Interview out-loud

“Scroll-driven animations use animation-timeline: scroll() or view() so keyframe progress tracks scrolling instead of time. view() is great for reveal-on-enter; scroll() for page progress bars. I gate with @supports, keep static styles as fallback, and disable under prefers-reduced-motion.”

Footguns

  1. Assuming Safari/Firefox parity without checking current baseline.
  2. Animating layout properties on scroll timelines — still costly.
  3. Ignoring reduced motion.
  4. Nested scrollers — wrong timeline subject.
  5. Combining animation-duration expectations — duration is less meaningful when the timeline is scroll-linked (progress is scroll-based).

Progress bar recipe

<div class="read-progress" aria-hidden="true"></div>
.read-progress {
  position: fixed;
  inset-inline: 0;
  inset-block-start: 0;
  height: 3px;
  background: var(--brand);
  transform-origin: 0 50%;
  transform: scaleX(0);
  animation: grow-x linear;
  animation-timeline: scroll(root block);
  z-index: 400;
}
@keyframes grow-x {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}
@media (prefers-reduced-motion: reduce) {
  .read-progress { animation: none; transform: none; display: none; }
}

Keep decorative; do not use as the only reading position indicator if users need precision — a percentage text readout can complement it for long docs tools.

Further reading

Related guides