CSS Scroll Animations and View Timelines: A Practical Guide

CSS scroll animations let an element’s animation progress follow the user’s scroll position instead of time. This is useful for progress indicators, reveal effects, parallax-like motion, and other interactions that feel tied to the page itself.

Quick answer: Scroll animations use scroll-timeline, view-timeline, and animation-range to connect a CSS animation to scrolling. A scroll timeline tracks a scroll container, while a view timeline tracks an element as it enters and leaves the viewport.

Difficulty: Intermediate

You’ll understand this better if you know: basic CSS animations, how overflow scrolling works, and how elements are sized and positioned in the layout.

1. What Is CSS Scroll Animations and View Timelines?

CSS scroll animations are animations whose progress is driven by scrolling instead of elapsed time. Instead of saying “run this animation for 3 seconds,” you say “run this animation as the user scrolls this container” or “as this element moves through the viewport.”

In practice, that means you can build effects that start, stop, and progress naturally as the user scrolls without writing scroll event code.

2. Why CSS Scroll Animations Matter

Scroll-driven motion can make interfaces feel clearer and more responsive because the animation matches what the user is already doing. For example, a progress bar can show how far through an article the reader has moved, or cards can reveal themselves as they enter the viewport.

These features matter because they move a common interaction pattern from script into CSS. That can reduce complexity, improve maintainability, and make the effect easier to reason about in stylesheets.

They are especially useful when you want:

3. Basic Syntax or Core Idea

At the core, you define a timeline and then tell an animation to use it. The animation no longer uses the default time-based timeline.

Basic scroll timeline example

This example links an animation to the page scroll progress. As the page scrolls, the element’s opacity changes.

.scroller {
  height: 100vh;
  overflow-y: auto;
  scroll-timeline-name: --page-scroll;
  scroll-timeline-axis: block;
}

.headline {
  animation-name: fade-in;
  animation-duration: 1s;
  animation-timeline: --page-scroll;
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

Here, the scroll container defines the timeline, and the animated element listens to that timeline. The animation still uses keyframes, but its progress is controlled by scrolling.

Basic view timeline example

A view timeline follows an element as it moves through the viewport. This is useful for reveal effects that begin when the element becomes visible.

.card {
  view-timeline-name: --card-view;
  view-timeline-axis: block;
  animation-name: slide-up;
  animation-timeline: --card-view;
}

@keyframes slide-up {
  from {
    opacity: 0;
    transform: translateY(24px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

This pattern is often used for cards, sections, and illustrations that should animate as they come into view.

4. Step-by-Step Examples

Example 1: Reading progress bar

A common scroll-timeline use case is a progress bar that fills as the page scrolls. The bar’s width follows the scroll position of the article container.

.article {
  height: 100vh;
  overflow-y: auto;
  scroll-timeline-name: --article-scroll;
  scroll-timeline-axis: block;
}

.progress {
  animation: fill-progress 1s linear both;
  animation-timeline: --article-scroll;
}

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

This works well because a transform-based fill is smooth and easy to animate. The timeline makes the bar reflect scroll progress instead of elapsed time.

Example 2: Reveal cards as they enter the viewport

View timelines are ideal for entrance effects. Each card can animate when it enters view, without needing a script to detect visibility.

.feature-card {
  view-timeline-name: --card;
  view-timeline-axis: block;
  animation-name: reveal;
  animation-timeline: --card;
  animation-range: entry 0% entry 100%;
}

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

The range starts the animation when the element begins entering and finishes it when the entry phase completes. That gives you a natural reveal tied to visibility.

Example 3: Parallax-like motion on a banner

You can use scroll progress to move a decorative element at a slower rate than the rest of the page. This creates a parallax-like effect without scripting.

.banner {
  scroll-timeline-name: --banner-scroll;
  scroll-timeline-axis: block;
}

.banner__shape {
  animation-name: float-shape;
  animation-timeline: --banner-scroll;
  animation-range: cover 0% cover 100%;
}

@keyframes float-shape {
  from {
    transform: translateY(0);
  }
  to {
    transform: translateY(-60px);
  }
}

This kind of motion works best for subtle decorative content. Strong motion can become distracting if it fights the content the user is trying to read.

Example 4: Section marker that activates near view center

Sometimes you want a nav indicator or highlight to change when a section passes through a specific part of the viewport.

.chapter {
  view-timeline-name: --chapter;
  view-timeline-axis: block;
}

.chapter-marker {
  animation-name: activate-marker;
  animation-timeline: --chapter;
  animation-range: contain 25% contain 75%;
}

@keyframes activate-marker {
  from {
    background-color: transparent;
  }
  to {
    background-color: rebeccapurple;
  }
}

This shows how animation range can target a middle portion of the element’s view progress rather than only the beginning or the end.

5. Practical Use Cases

These use cases are strongest when the motion is informational or subtle. They are less appropriate for critical UI states that must remain obvious without animation.

6. Common Mistakes

Mistake 1: Defining the timeline on the wrong element

Scroll timelines are created on the scroll container, not on an unrelated child element. If the element never scrolls, the animation will not have meaningful progress.

Problem: The animation listens to a timeline name that is never created on the actual scrolling container, so the effect appears not to work.

.content {
  animation-timeline: --page-scroll;
}

.headline {
  animation-name: fade-in;
  animation-duration: 1s;
}

Fix: Put the scroll timeline on the element that actually scrolls, then reference that timeline from the animated element.

.content {
  height: 100vh;
  overflow-y: auto;
  scroll-timeline-name: --page-scroll;
  scroll-timeline-axis: block;
}

.headline {
  animation-name: fade-in;
  animation-duration: 1s;
  animation-timeline: --page-scroll;
}

The corrected version works because the timeline is attached to the element that provides scroll progress.

Mistake 2: Using time-based expectations with a scroll-driven animation

A scroll-linked animation does not complete in a fixed number of seconds. Its progress depends on how far and how fast the user scrolls.

Problem: Expecting a scroll-linked animation to finish after a precise duration causes confusion, especially when the animation seems to pause whenever scrolling stops.

.card {
  animation-name: reveal;
  animation-duration: 2s;
  animation-timeline: --card-view;
}

Fix: Keep the duration as part of the animation syntax, but understand that the timeline determines progress. Use the duration as a scaling reference, not as a real clock.

.card {
  animation: reveal 1s both;
  animation-timeline: --card-view;
  animation-range: entry 0% entry 100%;
}

The corrected version works because the animation is explicitly tied to view progress, not wall-clock time.

Mistake 3: Forgetting that overflow and size determine scrollability

Scroll timelines need actual scrolling context. If the element cannot scroll, the timeline has no useful range.

Problem: The container has no fixed height or no overflow, so there is nothing to scroll and the animation appears frozen.

.panel {
  scroll-timeline-name: --panel-scroll;
}

.indicator {
  animation-timeline: --panel-scroll;
}

Fix: Give the container a scrollable area by setting a size and overflow.

.panel {
  height: 400px;
  overflow-y: auto;
  scroll-timeline-name: --panel-scroll;
  scroll-timeline-axis: block;
}

.indicator {
  animation-timeline: --panel-scroll;
}

The corrected version works because the timeline now has a real scroll range to measure.

7. Best Practices

Practice 1: Use scroll timelines for state-like progress, not constant motion

Scroll animations work best when they represent progress, reveal, or position. They are less effective for constant looping effects that should run independently of scrolling.

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

This approach keeps the animation meaningful because the visual change reflects user position.

Practice 2: Prefer subtle transforms and opacity changes

Performance and readability are usually better when you animate transform and opacity rather than layout properties. Those properties are easier for browsers to handle smoothly.

.reveal {
  animation-name: reveal;
}

@keyframes reveal {
  from {
    opacity: 0;
    transform: translateY(16px);
  }
}

These properties are usually enough to create polished scroll-driven motion without causing layout shifts.

Practice 3: Keep animation ranges intentional

The right animation-range makes the motion feel precise. If the effect starts too early or ends too late, tune the range instead of forcing the keyframes to compensate.

.card {
  animation-timeline: --card-view;
  animation-range: entry 20% cover 60%;
}

Thoughtful ranges make the effect line up with what the user is actually seeing.

8. Limitations and Edge Cases

If an animation appears to do nothing, first check that the browser supports scroll-driven animations, then confirm that the container actually scrolls and that the timeline name matches exactly.

9. Practical Mini Project

Here is a small article layout with a reading progress bar and reveal cards. It combines a scroll timeline for the progress indicator with view timelines for the cards.

.reader {
  height: 100vh;
  overflow-y: auto;
  scroll-timeline-name: --reader-scroll;
  scroll-timeline-axis: block;
}

.progress {
  position: sticky;
  top: 0;
  height: 4px;
  background: #ddd;
}

.progress__bar {
  height: 100%;
  background: #7c3aed;
  transform-origin: left;
  animation: fill-progress 1s linear both;
  animation-timeline: --reader-scroll;
}

.reveal-card {
  view-timeline-name: --card-view;
  view-timeline-axis: block;
  animation: reveal 1s both;
  animation-timeline: --card-view;
  animation-range: entry 0% cover 40%;
}

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

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

This mini project shows the main pattern: one scroll timeline for page progress and one view timeline for element entry. It is a good starting point for article layouts, documentation pages, and editorial interfaces.

10. Key Points

11. Practice Exercise

Expected output: A reading interface where the top bar fills with scroll progress and each card animates smoothly as it becomes visible.

Hint: Use one timeline name for the article container and a separate view timeline name for each card.

.reader {
  height: 100vh;
  overflow-y: auto;
  scroll-timeline-name: --reader-scroll;
  scroll-timeline-axis: block;
}

.progress__bar {
  animation: fill-progress 1s linear both;
  animation-timeline: --reader-scroll;
}

.card {
  view-timeline-name: --card-view;
  view-timeline-axis: block;
  animation: reveal 1s both;
  animation-timeline: --card-view;
  animation-range: entry 0% cover 40%;
}

12. Final Summary

CSS scroll animations let you tie motion to the user’s scrolling position, which makes interfaces feel more connected to the content being viewed. Use scroll-timeline when you want the animation to follow a scroll container, and use view-timeline when you want the animation to respond to an element entering or leaving the viewport.

For most projects, the best scroll-driven effects are subtle and purposeful. Reading bars, reveal animations, and section indicators are all strong candidates because the motion helps communicate position and progress rather than distracting from the page.

If you want to go further, the next step is to experiment with different animation-range values and compare page-level scroll timelines with view-based timelines in a simple article layout.