animation-timeline: scroll() / view()

Scroll-Driven Animations

animation-timeline drives an animation by scroll position instead of by time: how far a container has scrolled, or how far an element has travelled through the scrollport. That is a whole category of scroll-listener and IntersectionObserver code replaced by three lines of CSS.

Scroll inside the box. Every animation below is driven by scroll position, no JS.

  • Saturn

    Its rings are made of ice and rock, some pieces the size of a house.

  • Halley’s Comet

    Returns to the inner solar system roughly every 76 years.

  • The Moon

    Drifting away from Earth at about 3.8cm a year.

  • The ISS

    Orbits Earth every 90 minutes at roughly 28,000km/h.

  • Betelgeuse

    A red supergiant big enough to swallow Jupiter’s orbit.

  • Black holes

    Warp spacetime so much that light can’t escape.

The CSS

Copied
.progress-bar {
  transform-origin: left;
  animation: grow-progress linear;
  animation-timeline: scroll(nearest);
  animation-duration: auto;
}

.card {
  animation: reveal linear both;
  animation-timeline: view(block 20%);
  animation-range: entry 0% cover 30%;
}

How it works

animation-timeline: scroll(nearest) maps 0% to 100% of an animation onto the scroll offset of the nearest scrollable ancestor. That drives the progress bar and the spinning globe here, both just tracking wherever the box is scrolled to.

animation-timeline: view() maps progress to how far an element has travelled through its scroll container’s viewport, refined by animation-range (here entry 0% cover 30%, so each card finishes fading in shortly after it appears). The compositor runs both, so they stay smooth on a janky main thread.

Limited support

Chrome / Edge115+SafariFirefox

Behind a flag in Firefox; not yet in Safari.

:has() · tic-tac-toe

turns, wins and reset, all CSS

@property · counter()
hover me
cos() · sin()

trigonometry, in CSS

:has() quantity query

switch guests on