Shots in this scene
Named Timelines and timeline-scope
In one line
A timeline can have a name, so any element can use it. scroll-timeline-name and view-timeline-name create the name, animation-timeline: --name uses it, and timeline-scope makes a name visible outside the subtree where it was made. A misspelled name raises no error.
Think of it like this
A clock on a town square. People in the square read it, but a shop two streets away cannot see it. The town can put a repeater clock on the shop's wall, which shows the same time. A named timeline is the square's clock, and timeline-scope is the repeater: it makes the same clock readable where it could not be seen.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- named timeline
- A scroll or view timeline given a name like
--page, so thatanimation-timeline: --pagecan use it. - timeline-scope
- A property on an ancestor that makes a timeline name usable by all of its descendants, not only by the element that made the name.
- dashed ident
- A name that starts with two dashes (
--page). Timeline names and custom properties use them. - descendant
- An element inside another element, at any depth. A sibling is not a descendant.
Step by step
01Three elements, one name
In "One name, three places" the first bar has a ScrollTimeline. The typo bar and the outside bar have no timeline and are full.
02Scope it
The script adds body.scoped (timeline-scope: --page): the outside bar now has a ScrollTimeline. Toggle timeline-scope takes it away again.
03Two with the same name
In "Same name twice", nested timelines named --x give #inner. Two siblings named --y give no timeline.
04Debug with getAnimations
In the console, el.getAnimations()[0].timeline is null when a name did not resolve.
Try it yourself
- 1
Toggle the scope
In "One name, three places" scroll the box to 100 px: the third bar reads scaleX 0.26 (measured). Now press Toggle timeline-scope. This press removes the scope: the third bar loses its timeline, jumps to full. Press again: it has a
ScrollTimelineonce more, but in headless Chromium its animation comes back paused at scaleX 0 and ignores the scroll until you reload (measured at 100 and 150 px). Decide the scope in the stylesheet, not at run time.
Code & diagrams
Scroll the 90 px box and watch the first and (after scoping) third bars. The button toggles `timeline-scope` on body.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
inside the scroller: ScrollTimeline
typo --pgae: no timeline, bar is full: true
outside, no timeline-scope: no timeline, bar is full: true
outside, with timeline-scope on body: ScrollTimeline, source: sEdit the CSS: rename `#inner`'s timeline to `--z` and the bar follows `#outer`.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
nested, both named --x: the bar follows #inner
two scrollers named --y in one scope: no timelineBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Misspell the name in one place
In "One name, three places" change the .bar rule's animation-timeline: --page; to animation-timeline: --pge;.
Myth vs fact
Myth
A named timeline is global: any element on the page can use it.
Fact
It is visible only below the element that made it. Use timeline-scope on a common ancestor to widen it.
Remember this
- 1
The problem this solves:
scroll()andview()make an anonymous timeline for the element that uses them. Often something else should follow a scroller: a header bar driven by a long article, a caption driven by a card. That element is outside the scroller, so it needs a way to point at someone else's timeline. - 2
Make a name. On the scroller:
scroll-timeline: --page block(name, then axis; orscroll-timeline-nameand-axis). On a subject:view-timeline: --volt block(orview-timeline-name,-axis,-inset). A name starts with two dashes, like a custom property, andanimation-timeline: --pageuses it. It is the same timelinescroll()orview()makes; only the name is new. - 3
A name is visible only below its element. The element that defines a name, and all its descendants, can use it. Siblings and parents cannot: a sticky bar inside the scroller works, a meter after it does not (first example), and the browser raises no error.
- 4
**
timeline-scopelifts the name.** Puttimeline-scope: --pageon a common ancestor (herebody): every descendant can then use--page, including the meter outside the scroller. In the first example a button toggles it onbody: with it the meter follows the scroller, without it the meter has no timeline. - 5
A wrong name does nothing, quietly. A misspelled name (
--pgae) matches no timeline, with no console error. Measured: the animation reportsfinished, and with fill-modeboththe last keyframe stays, so the bar is drawn full. A bar full from the first frame is the usual sign, andel.getAnimations()[0].timelineisnull. - 6
Two timelines with one name. Nested, the nearest ancestor wins: in the second example the bar follows
#inner, and after renaming the inner one,#outer. Two siblings that share a name in onetimeline-scopeare ambiguous, and measured here the animation gets no timeline. Use unique names.
Explain it without notes
Why does a meter placed after a scroller not see the scroller's named timeline, and how do you fix it?
A bar is full from the first frame and the console is silent. What do you check?
Practice
A header progress bar must follow the scroll of the <main> element (not the page). Write the three CSS rules.
A list of cards each has its own view timeline named --card. Why will a single animation-timeline: --card on a sticky counter not follow the one in view, and what is a safer design?
Done when you can
You can name a scroll or view timeline and use the name in
animation-timelineYou can say who can see a name, and use
timeline-scopeto widen itYou know a misspelled name is silent and leaves the animation at its last keyframe
You can debug a timeline with
getAnimations()[0].timeline