Advanced Scroll Timeline: Named Timelines, View Timeline and animation-range
AI generated
{ }
@
CSS · Scroll Timeline · View Timeline · Animation
Advanced Scroll Timeline
Named Timelines, View Timeline and animation-range in Detail

Almost every project already knows a simple fade-in on scroll. Scroll timeline becomes genuinely interesting once multiple axes, named timelines and precise animation-range values work together to build complex narrative layouts entirely without scroll event listeners.

18 min read scroll-timeline-name · view-timeline · animation-range · timeline-scope Chrome 115+ · progressive enhancement

1. Why named scroll timelines need advanced control

The simplest form of a scroll timeline couples an animation directly to the scroll progress of the nearest scrolling ancestor: animation-timeline: scroll();. For a progress bar or a simple parallax element that is entirely sufficient. But as soon as multiple independent scroll containers exist on a page, for example a horizontally scrolling carousel section next to the vertical main document, the anonymous timeline becomes ambiguous: which animated element should reference which container?

This is exactly where named scroll timelines come in. With scroll-timeline-name any scrolling element gets a unique identifier that any other element in the document can then reference via animation-timeline: --my-name;, independent of DOM position. This decoupling of source and target is the key difference between a simple scroll effect and a genuinely advanced scroll timeline architecture.

A practical example: a progress bar at the top of the screen should show reading progress for an article section that does not scroll itself but sits inside a container further down. Without a named timeline the bar would have to be a child of the scrolling container. With scroll-timeline-name the bar can live anywhere in the document and still be coupled exactly to the container's scroll progress.

2. scroll-timeline-name and scroll-timeline-axis in detail

The shorthand scroll-timeline: --progress block; sets name and axis in one step. scroll-timeline-axis determines whether the timeline follows progress along the block axis (mostly vertical) or the inline axis (mostly horizontal). For horizontally scrolling carousels or storytelling sections with sideways scroll, inline is the right choice, for the vast majority of classic pages block.

Important for advanced scroll timeline setups: the name itself is a custom-ident, so it starts with two dashes, exactly like a CSS custom property. Multiple elements in the document can use the same name without conflict, as long as every referencing element clearly knows which timeline is meant. In practice a descriptive, unique naming scheme per section is still recommended, such as --hero-scroll or --gallery-scroll, to avoid confusion in larger stylesheets.


/* Scrolling container defines the named timeline */
.gallery-track {
  overflow-x: auto;
  scroll-timeline: --gallery-scroll inline;
}

/* Any element in the document can subscribe to it */
.gallery-progress-bar {
  animation: fill-bar linear;
  animation-timeline: --gallery-scroll;
  animation-range: 0% 100%;
}

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

A common mistake when migrating from anonymous to named scroll timelines: setting scroll-timeline-axis without overflow on the same element. The timeline strictly requires an actually scrolling container, an element without overflow: auto or overflow: scroll does not produce a valid scroll timeline, even if name and axis are declared correctly.

3. Using view-timeline for element visibility

While scroll-timeline measures the progress of a container, view-timeline measures the visibility progress of a single element within its nearest scrolling ancestor. The element itself becomes the source of the timeline: as soon as it enters the visible area the timeline starts at 0 percent, once it fully leaves the visible area it ends at 100 percent. That is exactly the pattern needed for reveal animations, sticky highlight effects or scrollytelling sections.

The shorthand view-timeline: --card-reveal block; on the observed element activates this visibility timeline directly. Combined with view-timeline-inset the visibility range can be shifted further, for example to only trigger an animation once an element already sits a bit inside the viewport, instead of right at its edge.


.story-card {
  view-timeline: --card-reveal block;
  view-timeline-inset: 10% 0%;
}

.story-card {
  animation: reveal-card linear both;
  animation-timeline: --card-reveal;
  animation-range: entry 0% cover 40%;
}

@keyframes reveal-card {
  from { opacity: 0; transform: translateY(48px) scale(0.94); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

The decisive difference to a simple fade-in script with IntersectionObserver: view-timeline is fully scrubbable. The animation progress follows the scroll progress exactly, in both directions, without any JavaScript callback chain in between. If the user scrolls fast, the animation follows just as fast, scroll back and the animation runs in reverse, all controlled purely by the browser's compositor thread.

4. animation-range: setting start and end precisely

animation-range is the building block that turns a rough visibility timeline into a precisely choreographed scroll timeline animation. Instead of the entire visible duration of an element, it defines exactly which slice of the timeline an animation runs in. The named ranges entry, exit, cover and contain describe different phases of the visibility progression and can be combined with percentage values.

entry 0% entry 100%, for example, describes exactly the phase where an element goes from completely invisible to completely visible, while cover 0% cover 100% describes the entire timespan during which any part of the element is visible. These ranges can be mixed freely: animation-range: entry 20% cover 60%; starts the animation once the element has reached 20 percent of its entry phase and ends it once 60 percent of the cover phase is reached.


/* Precise choreography: fade during entry, hold, then fade during exit */
.section-heading {
  view-timeline: --heading-view block;
  animation: heading-choreography linear both;
  animation-timeline: --heading-view;
  animation-range: entry 0% exit 100%;
}

@keyframes heading-choreography {
  0%   { opacity: 0; filter: blur(6px); }
  20%  { opacity: 1; filter: blur(0); }
  80%  { opacity: 1; filter: blur(0); }
  100% { opacity: 0; filter: blur(6px); }
}

The advantage of this precise animation-range control over a crude all-or-nothing fade: a section can stay stable for the entire visible period and only animate at the transitions, which feels far calmer for text blocks than an effect running across the full visibility duration. Anyone wanting to ship scroll timeline effects to production cannot skip this level of fine tuning.

5. Combining multiple timelines: parallax across several axes

Classic parallax shifts layers at different speeds relative to scroll progress. With multiple scroll timelines active at once, this effect can be built entirely without JavaScript by giving each layer its own animation-timeline with a different animation-range or a different keyframe distance. The background layer moves a few percent across the entire scroll range, the foreground layer moves several times more across the exact same range.

For genuine multi-axis effects, where a layer shifts vertically while rotating horizontally at the same time, two independent animations on the same element can even be coupled to the same timeline, each with its own animation-range. The result is a choreographed effect that at first glance looks like complex JavaScript but is in fact entirely declarative in the stylesheet.


.parallax-section {
  scroll-timeline: --parallax-scroll block;
}

.parallax-bg {
  animation: shift-bg linear both;
  animation-timeline: --parallax-scroll;
  animation-range: 0% 100%;
}
.parallax-mid {
  animation: shift-mid linear both;
  animation-timeline: --parallax-scroll;
  animation-range: 0% 100%;
}
.parallax-fg {
  animation: shift-fg linear both, rotate-fg linear both;
  animation-timeline: --parallax-scroll, --parallax-scroll;
  animation-range: 0% 100%, 10% 90%;
}

@keyframes shift-bg { to { transform: translateY(6%); } }
@keyframes shift-mid { to { transform: translateY(18%); } }
@keyframes shift-fg  { to { transform: translateY(40%); } }
@keyframes rotate-fg { to { transform: rotate(4deg); } }

The performance advantage should not be underestimated: all three layers run on the compositor thread without the main thread having to recompute anything on every scroll frame. A classic JavaScript parallax with scroll event listeners, in contrast, produces a layout thrash candidate on every single scroll frame as soon as getBoundingClientRect() gets called inside the handler.

6. timeline-scope for cross-element control

By default a named scroll timeline or view-timeline is only visible within its own subtree, meaning descendants of the element that declares it. For cases where an element outside that subtree should react to the timeline, for example a progress indicator in the header reacting to a view-timeline of an element far down the document, there is timeline-scope.

Declared on a shared ancestor, such as :root or a layout wrapper, timeline-scope extends the visibility of a named timeline to the entire scope, regardless of actual DOM nesting. This is what makes complex scrollytelling layouts possible, where control elements and animated targets sit at completely different places in the document.


:root {
  timeline-scope: --hero-view;
}

.hero-section {
  view-timeline: --hero-view block;
}

/* Header lives outside .hero-section in the DOM, but can still react */
.site-header .scroll-indicator {
  animation: shrink-indicator linear both;
  animation-timeline: --hero-view;
  animation-range: 0% 100%;
}

7. Fallback with @supports and IntersectionObserver

Scroll timelines are currently less widely supported than basic CSS animations. For projects that depend on broader browser coverage, a clear separation via @supports (animation-timeline: view()); is recommended. Inside that block sit all scroll timeline declarations, outside it a simple IntersectionObserver toggles the same CSS class, but without scrubbing capability.

It is important that both code paths share the same visual foundation, so no break in appearance occurs. In practice this means: the start and end state of the animation get defined as regular CSS classes, and both the scroll timeline variant and the IntersectionObserver fallback simply toggle between these classes instead of maintaining completely different animation logic.


// Fallback only runs when native scroll-driven animations are unsupported
if (!CSS.supports('animation-timeline: view()')) {
  const observer = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
      entry.target.classList.toggle('is-revealed', entry.isIntersecting);
    });
  }, { threshold: 0.4 });

  document.querySelectorAll('.story-card').forEach((card) => {
    observer.observe(card);
  });
}

8. Debugging scroll timelines in DevTools

Broken scroll timeline setups usually show one of two symptoms: the animation does not run at all, or it runs only once instead of staying scrubbable. The most common cause is a missing overflow value on the timeline source element, followed by a typo in the custom-ident name between source and target. Chromium based DevTools show active scroll timelines under "Animations" in the element panel, with current progress and a visual scrubber.

A second typical mistake concerns timing functions: a scroll timeline animation using ease-in-out instead of linear looks uneven on close inspection, because the easing curve was designed for time-based animations, not scroll-progress-based ones. For most scroll timeline cases, linear is the correct timing function, the actual acceleration or deceleration comes from the keyframe distribution itself, not from the easing function.

9. Scroll timeline patterns compared

Choosing the right technique benefits from a direct comparison of the common approaches for scroll-coupled animations, from a simple anonymous timeline all the way to a fully choreographed multi-timeline setup.

Pattern Control JavaScript required Use case
Anonymous scroll() timeline Low No Simple progress bar
Named scroll-timeline Medium No Target elements outside the container
view-timeline + animation-range High No Precisely choreographed reveal effects
timeline-scope High No Cross-element scrollytelling
IntersectionObserver Low, no scrubbing Yes Fallback for older browsers

For new projects that can prioritize modern browsers, view-timeline with precise animation-range is the clear favorite: maximum control, no runtime cost on the main thread, no extra JavaScript dependency. Only when broad legacy support is mandatory does IntersectionObserver remain relevant as a robust but less fluid fallback.

Mironsoft

Scrollytelling, parallax and performant animation for modern frontends

Scroll effects that run smoothly without JavaScript?

We build precisely choreographed scroll timeline animations with view-timeline and animation-range, including a fallback strategy for older browsers and a full performance audit.

Concept

Planning scrollytelling layouts and designing the timeline structure

Implementation

Named timelines, view-timeline and precise animation-range

Quality assurance

Fallback tests, compositor performance and debugging setup

10. Summary

Advanced scroll timeline technique goes well beyond a single fade-in on scroll. scroll-timeline-name decouples the source and target of a timeline, view-timeline couples an animation to the visibility of an element instead of a container, and animation-range with the values entry, exit, cover and contain allows precise choreography of individual animation phases.

Multiple timelines active at once enable fully declarative parallax effects with different speeds per layer, timeline-scope solves the problem of control elements sitting outside their own subtree. Because all of these effects run on the compositor thread, scroll performance stays stable even in complex setups, entirely without scroll event listeners and without the associated main thread cost. An @supports fallback with IntersectionObserver secures the feature on browsers without support.

Advanced Scroll Timeline — Key Takeaways

Named timelines

scroll-timeline-name decouples source and animated target, independent of DOM position.

View timeline

view-timeline couples to the visibility of an element, ideal for reveal effects and scrollytelling.

Precise animation-range

entry, exit, cover, contain enable fine choreography instead of all-or-nothing fades.

Performance

Compositor thread instead of scroll event listeners. No layout thrash, no main thread overhead.

11. FAQ: Advanced Scroll Timeline

1scroll-timeline vs. view-timeline?
scroll-timeline measures container progress, view-timeline measures the visibility of a single element.
2What does scroll-timeline-name do?
Gives the timeline a unique name so any element can reference it, independent of DOM position.
3entry, exit, cover, contain?
Different visibility phases: entry while entering, exit while leaving, cover for visible parts, contain when everything is visible.
4My timeline does not work?
Usually overflow is missing on the source element, or the name does not match between source and target.
5Which timing function to use?
Almost always linear. Easing curves are meant for time, not for scroll-based progress.
6Parallax without JavaScript?
Couple multiple layers to the same timeline, each with its own keyframe distance or animation-range.
7What is timeline-scope for?
Lets an element outside the timeline source's subtree still react to it.
8Fallback for older browsers?
Check CSS.supports('animation-timeline: view()'), otherwise use an IntersectionObserver toggling the same classes.
9Debugging in the browser?
DevTools show active timelines with progress under Animations in the element panel. Missing overflow is the most common cause.
10Is view-timeline scrubbable?
Yes, fully, in both scroll directions, without any JavaScript callback chain.