Skip to main content

CSS Scroll-Driven Animations

Link CSS animations to scroll position using
animation-timeline: scroll()

What are Scroll-Driven Animations?

Scroll-driven animations allow you to link CSS animations to scroll position instead of time. As the user scrolls, the animation progresses - no JavaScript needed.

Two Types of Scroll Timelines

Scroll Progress Timeline

Links animation to scroll position of a scroll container. Think: progress bars, parallax effects.

animation-timeline: scroll()

View Progress Timeline

Links animation to an element's visibility in the viewport. Think: reveal animations, sticky effects.

animation-timeline: view()

Browser Support

Scroll-driven animations are currently supported in Chromium browsers (Chrome 115+, Edge 115+). Safari and Firefox do not yet support this feature.

Chrome 115+Edge 115+Safari - NoFirefox - No

Use as progressive enhancement. Non-supporting browsers will see static content or time-based fallback animations.

Scroll Progress Indicator

The classic use case: a progress bar that fills as you scroll down the page.

How It Works

1. Define a standard keyframe animation (scale from 0 to 1)

2. Set animation-timeline: scroll()

3. The animation progress now tracks scroll position

4. At top of page = 0%, at bottom = 100%

CSS Code

/* Scroll progress indicator */
.progress-bar {
  position: fixed;
  top: 0;
  left: 0;
  height: 4px;
  background: linear-gradient(to right, #667eea, #764ba2);
  transform-origin: left;
  animation: grow-progress linear;
  animation-timeline: scroll();
}

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

scroll() Function Syntax

The scroll() function specifies which scroll container to track.

Syntax Reference

/* scroll() function syntax */
animation-timeline: scroll();              /* Nearest scroller, block axis */
animation-timeline: scroll(root);          /* Root scroller (viewport) */
animation-timeline: scroll(nearest);       /* Nearest ancestor scroller */
animation-timeline: scroll(self);          /* Element itself if scrollable */

/* With axis */
animation-timeline: scroll(root block);    /* Vertical scroll */
animation-timeline: scroll(root inline);   /* Horizontal scroll */
animation-timeline: scroll(root x);        /* X axis */
animation-timeline: scroll(root y);        /* Y axis */

/* Named scroll timeline */
.scroll-container {
  scroll-timeline-name: --my-scroller;
  scroll-timeline-axis: block;
  /* Shorthand: scroll-timeline: --my-scroller block; */
}

.animated-element {
  animation-timeline: --my-scroller;
}
Scroller ValueDescription
rootThe document viewport
nearestNearest scrollable ancestor (default)
selfThe element itself

View Progress Timeline

view() tracks an element's visibility in the viewport, perfect for scroll-reveal animations.

Animation Range

The animation-range property defines when the animation starts and ends relative to the element's visibility.

Range NameDescription
coverFrom first visible pixel to last (full travel)
containWhile fully visible in viewport
entryWhile entering the viewport
exitWhile exiting the viewport
entry-crossingWhile crossing the entry edge
exit-crossingWhile crossing the exit edge

CSS Code

/* Reveal animation when element enters viewport */
.reveal-element {
  opacity: 0;
  transform: translateY(50px);
  animation: reveal linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 100%;
}

@keyframes reveal {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Image zoom as it scrolls through viewport */
.zoom-image {
  animation: zoom linear both;
  animation-timeline: view();
  animation-range: contain 0% contain 100%;
}

@keyframes zoom {
  from { transform: scale(0.8); }
  to { transform: scale(1); }
}

Parallax Scrolling

Create parallax effects by animating elements at different rates relative to scroll.

The Technique

  1. Create a named scroll timeline on the scroll container
  2. Reference that timeline from child elements
  3. Use different translateY distances for each layer
  4. Slower-moving elements appear farther away

CSS Code

/* Parallax scrolling effect */
.parallax-container {
  scroll-timeline-name: --parallax;
  overflow-y: scroll;
  height: 100vh;
}

.parallax-slow {
  animation: parallax-slow linear;
  animation-timeline: --parallax;
}

.parallax-fast {
  animation: parallax-fast linear;
  animation-timeline: --parallax;
}

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

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

Sticky Header Animation

A common pattern: header that shrinks and gains a background as you scroll.

Key Concept: animation-range

Use animation-range: 0 200px to complete the animation within the first 200px of scroll, then hold the final state.

The forwards fill mode keeps the animation at its end state after the range completes.

CSS Code

/* Header that shrinks on scroll */
.header {
  position: sticky;
  top: 0;
  animation: shrink-header linear forwards;
  animation-timeline: scroll();
  animation-range: 0 200px;
}

@keyframes shrink-header {
  from {
    padding: 2rem 1rem;
    background: transparent;
  }
  to {
    padding: 0.5rem 1rem;
    background: white;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
  }
}

/* Logo shrinks with header */
.header-logo {
  animation: shrink-logo linear forwards;
  animation-timeline: scroll();
  animation-range: 0 200px;
}

@keyframes shrink-logo {
  from { height: 60px; }
  to { height: 40px; }
}

Polyfill for Broader Support

The scroll-timeline polyfill provides support for Firefox and Safari while native support is pending.

Using the Polyfill

<!-- Include polyfill for non-Chrome browsers -->
<script src="https://flackr.github.io/scroll-timeline/dist/scroll-timeline.js"></script>

<!-- Your CSS works the same way -->
<style>
.animated {
  animation: fade-in linear;
  animation-timeline: scroll();
}
</style>

Progressive Enhancement Strategy

  1. Design for no animation (content still works)
  2. Add scroll-driven animations in CSS
  3. Optionally include polyfill for broader support
  4. Test in all target browsers

CSS vs JavaScript Scroll Animations

AspectCSS Scroll-DrivenJavaScript (Intersection/Scroll)
PerformanceRuns on compositorMain thread
Code locationCSS onlyJS + CSS
ComplexityLower (declarative)Higher (imperative)
Browser supportChrome onlyAll browsers
Fine controlLimitedFull control

When to Use CSS Scroll Animations

  • Progress indicators and scroll-linked transforms
  • Simple reveal animations on scroll
  • Parallax effects
  • Header shrink effects

When to Use JavaScript

  • Complex multi-element orchestration
  • Conditional animations based on data
  • Animations requiring broad browser support today
  • Fine-grained scroll position callbacks