CSS Scroll-Driven Animations in 2026: A Practical Way to Replace Scroll Listeners


TL;DR: CSS scroll-driven animations let the browser connect an animation directly to page scroll position or an element’s journey through a scroll container. For progress indicators, reveal effects, image zooms, and section-driven storytelling, they can remove fragile JavaScript scroll handlers and reduce main-thread work. Use progressive enhancement, respect reduced-motion preferences, and keep effects purposeful.

Scroll-linked motion has been a familiar web pattern for years. Reading-progress bars, cards that fade in as they enter view, product images that gently scale, and long-form landing pages all use it. The usual implementation has been JavaScript: listen for scroll, measure positions, calculate progress, and write styles back to the page.

That approach works, but it is easy to get wrong. Scroll events can fire frequently, layout reads can force expensive work, and an effect that looks smooth on a developer laptop can feel sticky on a phone. In 2026, CSS scroll-driven animations offer a cleaner model for a growing set of these interactions.

They are not a mandate to animate every page. They are a useful platform feature when motion clarifies progress, hierarchy, or cause and effect.

Table of contents

  1. What scroll-driven animations are
  2. The two timeline types
  3. A reading-progress bar
  4. Element reveal animations
  5. Why this can outperform JavaScript scroll code
  6. Progressive enhancement and accessibility
  7. A production checklist
  8. FAQ

What scroll-driven animations are

Traditional CSS animations run against time. An animation may last 400 milliseconds, then finish regardless of what the user is doing.

A scroll-driven animation runs against a timeline whose progress comes from scrolling. At the top of a document the timeline might be at 0%; farther down the document it advances towards 100%. Alternatively, a timeline can represent an element moving through a scrollport, such as a feature card entering and leaving view.

The important architectural shift is that the browser owns the relationship:

  • CSS defines the visual states.
  • A scroll timeline supplies progress.
  • The rendering engine can coordinate the animation without an application-level scroll loop calculating every frame.

This is a more declarative approach. You describe what progresses with scroll, instead of wiring scroll events to style mutations yourself.

The two timeline types

The API has two primary mental models.

Scroll progress timelines

A scroll progress timeline maps the scroll position of a container to animation progress. This is ideal when the whole page or a specific scrolling panel is the source of truth.

A reading-progress indicator is the canonical example. It should grow exactly as the document is read, not on an arbitrary timer.

The concise form uses animation-timeline: scroll():

css
.reading-progress {
  position: fixed;
  inset: 0 auto auto 0;
  width: 100%;
  height: 4px;
  background: linear-gradient(90deg, #6d5dfc, #19c5c1);
  transform: scaleX(0);
  transform-origin: left;

  animation: grow-progress linear both;
  animation-timeline: scroll(root block);
}

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

root block identifies the document scroll container and its vertical writing-mode axis. The animation is linear because scroll position, rather than elapsed time, supplies the pacing.

View progress timelines

A view progress timeline follows an element’s visibility range inside a scroll container. It is usually the better fit for content that should animate when it passes through the viewport: an article illustration, a gallery card, or a feature section.

css
.feature-image {
  width: 100%;
  border-radius: 1rem;
  animation: image-enter linear both;
  animation-timeline: view();
  animation-range: entry 10% cover 45%;
}

@keyframes image-enter {
  from {
    opacity: 0;
    transform: scale(0.94) translateY(24px);
  }

  to {
    opacity: 1;
    transform: scale(1) translateY(0);
  }
}

animation-range is where this becomes expressive. It says when the animation should occur within the element’s journey. In this example, the image begins shortly after entering and completes as it covers a meaningful part of the viewport.

The right range is a design decision, not a magic number. A short, subtle range makes an entrance feel responsive. A longer range creates a more editorial, cinematic effect. Test it with real content lengths and screen sizes.

A reading-progress bar

A progress bar is a good first production use because its meaning is obvious. It tells users where they are in an article and does not interfere with reading.

Start with semantic, non-blocking markup:

html
<div class="reading-progress" aria-hidden="true"></div>

Then use the CSS from the earlier example. There is no JavaScript state, no resize observer, and no calculation involving document.documentElement.scrollHeight.

A few practical details matter:

  • Keep it thin enough that it does not compete with navigation.
  • Use a solid, accessible colour or a restrained gradient with sufficient contrast against the page edge.
  • Set pointer-events: none if the indicator overlaps fixed controls.
  • Treat it as decoration with aria-hidden="true"; it is not an accurate substitute for document structure or a table of contents.

For a nested scroll area, name the timeline instead of relying on the root:

css
.article-panel {
  overflow-y: auto;
  scroll-timeline-name: --article-scroll;
  scroll-timeline-axis: block;
}

.article-panel .reading-progress {
  animation-timeline: --article-scroll;
}

Named timelines make component boundaries clearer. They also prevent a reusable component from accidentally binding to the page when it should respond to its own container.

Element reveal animations

Scroll-driven animation is especially useful for effects that previously needed IntersectionObserver plus CSS classes. IntersectionObserver remains an excellent tool when entering view needs to trigger application logic, lazy loading, analytics, or a one-time data fetch. But if the only outcome is visual, CSS can be simpler.

Consider a grid of case-study cards:

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

@keyframes card-reveal {
  from {
    opacity: 0;
    transform: translateY(1rem);
  }

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

Avoid assigning a dramatic animation to every item in a dense interface. Repeated movement makes scanning harder and can turn a tidy grid into visual noise. Use it for a small number of high-value elements or a deliberate storytelling sequence.

Also prefer animating transform and opacity. They are generally friendlier to rendering performance than properties that affect layout, such as height, top, or margin. A smooth animation is not just about choosing a modern API, it is about choosing inexpensive properties and limiting the number of simultaneously animated elements.

Why this can outperform JavaScript scroll code

The advantage is not that JavaScript is inherently bad. Plenty of scroll experiences still need JavaScript, especially canvas work, stateful interactions, or physics-based effects. The advantage is that CSS reduces the amount of coordination your application owns.

A typical hand-rolled implementation must answer questions like:

  1. Which container is actually scrolling?
  2. How is its maximum scroll position calculated?
  3. What happens when images load and document height changes?
  4. How often should work run, and is it throttled with requestAnimationFrame?
  5. Are reads and writes separated to avoid layout thrashing?
  6. How do we clean up listeners when a component unmounts?

A native timeline answers much of that at the platform level. It can also keep visual code close to the styles it controls, which is easier to review and remove later.

That said, measure rather than assume. A page can still be slow because of oversized images, expensive shadows, or too many independently animating layers. The API removes a common source of scroll work, it does not grant a performance free pass.

Progressive enhancement and accessibility

Browser support should shape how you deploy scroll-driven animations. For an enhancement such as a reveal or a progress bar, the fallback can simply be the fully visible, static design. Never make content inaccessible or dependent on an animation completing.

Use a feature query to isolate the enhanced version:

css
.case-study-card {
  opacity: 1;
  transform: none;
}

@supports (animation-timeline: view()) {
  .case-study-card {
    animation: card-reveal linear both;
    animation-timeline: view();
    animation-range: entry 0% cover 30%;
  }
}

This ordering is important. The baseline is readable content. Supported browsers add motion. Unsupported browsers still get the finished visual state.

Respect motion preferences too:

css
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}

You may prefer a narrower selector in a large application, but the principle is the same: when a user requests reduced motion, do not replace one effect with a nearly invisible version of the same effect. Remove the nonessential movement.

Finally, test keyboard navigation. A focused link or button must never become difficult to find because its parent is faded, transformed far away, or mid-animation.

A production checklist

Before shipping a scroll-driven effect, ask:

  • Does it explain something? Progress, hierarchy, or spatial relationship are good reasons. Decoration alone needs a high bar.
  • Does the static version work? The design must be complete without timeline support.
  • Is the motion restrained? Keep distance, scale, and duration-equivalent range modest.
  • Are you animating compositor-friendly properties? Start with opacity and transform.
  • Have you checked reduced motion? Make it an acceptance criterion, not a late accessibility patch.
  • Have you tested nested scrollers? Dashboards and mobile drawers often scroll somewhere other than the document.
  • Have you profiled the real page? Check a mid-range mobile device and a content-heavy route, not only a clean demo.

Where this fits in a modern frontend stack

This feature is framework-agnostic. In React, Next.js, Vue, or plain HTML, it belongs in the same place as any presentation concern: component CSS or a well-scoped stylesheet. You do not need a client component merely to animate a reading bar, which is useful in server-rendered applications where every unnecessary client dependency increases complexity.

The larger lesson is valuable beyond this API. Mature frontend work is often about deleting coordination code. When the platform can express a visual relationship directly, you gain less state to synchronize, fewer lifecycle edges, and a clearer fallback path.

CSS scroll-driven animations are ready for careful use in the parts of a product where progressive enhancement is acceptable. Start with one meaningful interaction, make the no-animation experience excellent, and let the browser do the scrolling math.

FAQ

Are CSS scroll-driven animations supported everywhere?

Support varies by browser and version, so treat them as progressive enhancement. Use @supports and ensure the default state is complete and readable.

Should I replace IntersectionObserver with CSS view timelines?

Only for purely visual effects. Keep IntersectionObserver when visibility must trigger JavaScript behavior such as fetching data, tracking exposure, or controlling application state.

Do scroll-driven animations eliminate all scroll performance problems?

No. They remove the need for many custom scroll listeners, but images, layout-heavy properties, excessive effects, and costly rendering can still make scrolling slow.

What properties should I animate?

Prefer transform and opacity for reveal, scale, and movement effects. Avoid continuously animating layout-affecting properties unless you have tested the cost on representative devices.

Frequently Asked Questions

Are CSS scroll-driven animations supported everywhere?

Support varies by browser and version, so use them as progressive enhancement with a complete static fallback.

Should CSS view timelines replace IntersectionObserver?

Use view timelines for purely visual effects. Keep IntersectionObserver when entering view must trigger JavaScript behavior or application state.

Which properties are best for scroll-driven animations?

Prefer transform and opacity, then test on representative devices. They are generally less expensive than layout-affecting properties.