Shots in this scene
View Progress Timelines and Ranges
In one line
A view progress timeline measures how far one element has travelled across its scroller's visible area. animation-timeline: view() makes it, and animation-range chooses the part of the journey (entry, contain, exit or cover) over which the animation plays.
Think of it like this
A train passing a platform. For you, standing on the platform, the train has a start (the nose reaches you), a middle (it fills your view) and an end (the tail leaves). What matters is the train's own journey past you, not the clock. A view timeline measures that journey for an element crossing the visible area of a scroller.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- subject
- The element a view timeline follows: the one that has
animation-timeline: view()(or the element passed tonew ViewTimeline). - scrollport
- The visible area of a scroller: the box you look through, without the part scrolled out of sight.
- range
- The part of a timeline an animation uses, written as
animation-range. A name (entry) or a name and a percentage (entry 50%). - inset
- A distance that shrinks the scrollport at its edges, so a view animation starts later and ends sooner.
Step by step
01Scroll one card in
In "Cards that fade in as they arrive" press Next twice: each card rises and turns opaque as it enters, done when fully inside.
02Read the range table
Each row is a range. For the 40 px subject entry reaches 1.00 at offset 240, contain at 300 and exit at 340.
03Compare short and tall
For the 160 px subject contain reads 0.72 at 340 and 1.00 at 360: it plays only while the subject covers the box.
04Read the edges
The first box is at 54% before any scrolling. The plain range is 200 to 348 px and the inset one 220 to 328 px.
Try it yourself
- 1
Add the two crossing ranges
In "The same animation over each range" change the list
["entry", "contain", "exit", "cover"]to["entry-crossing", "exit-crossing"]. Measured: for the 40 px subject they print the same rows asentryandexit. For the 160 px subject,entry-crossingreads 0.15, 0.36, 0.68, 0.89 and then 1.00 (from offset 360), slower thanentry, which is done at 300.
Code & diagrams
Each card has its own view timeline. Press Next (it scrolls 70 px) and read the Scroll tracks on the Motion Timeline.
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
animations: 3
timeline: ViewTimeline · subject: 80 km range · axis: block
animation-range: entryTry it: interactive
Drag the range
Pick a range (cover, contain, entry, exit and the two crossing ranges) and change the subject's height. Watch where the start and end fall, and the progress readout.
The script scrolls a 100 px box to seven offsets and prints the opacity (0.15 before the range, 1 after). The animation is `fade`, from opacity 0.15 to 1. It takes about two seconds.
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
scroll offsets: 200 240 300 340 360 400 460
subject 40px, entry: 0.15 1.00 1.00 1.00 1.00 1.00 1.00
subject 40px, contain: 0.15 0.15 1.00 1.00 1.00 1.00 1.00
subject 40px, exit: 0.15 0.15 0.15 1.00 1.00 1.00 1.00
subject 40px, cover: 0.15 0.39 0.76 1.00 1.00 1.00 1.00
subject 160px, entry: 0.15 0.49 1.00 1.00 1.00 1.00 1.00
subject 160px, contain: 0.15 0.15 0.15 0.72 1.00 1.00 1.00
subject 160px, exit: 0.15 0.15 0.15 0.15 0.15 0.49 1.00
subject 160px, cover: 0.15 0.28 0.48 0.61 0.67 0.80 1.00Three small scrollers. The first box is already in view at load. The third uses `view(block 20px)`.
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
in view at load: progress 54% before any scrolling
plain: range 200px to 348px
inset 20px: range 220px to 328pxBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Use exit where you meant entry
In "Cards that fade in as they arrive" change the range to animation-range: exit 0% exit 100%;.
Myth vs fact
Myth
view() fires when an element enters the screen.
Fact
It does not fire anything. It is a clock whose time is the element's position, so the animation is at a point on its journey all the time, forwards and backwards.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
The progress is exact at every scroll position, so there is no "already past it" problem: load a page scrolled half way, or press Back and have the browser restore the scroll, and every reveal already shows its finished state. Event-based reveals have to handle that case by hand (Topic 12.5).
Remember this
- 1
The problem this solves: a scroll progress timeline (Topic 12.1) measures a whole scroller, so it cannot say when one card arrives.
view()gives every element its own timeline, so its animation plays exactly while that element enters, with no JavaScript (the alternative isIntersectionObserver, Topic 12.5). - 2
**
view()follows one element, the subject,** against the nearest scroller's visible area (the scrollport). 0% is when the subject's first edge touches the scrollport, 100% when its last edge has left.view(block 20px)also takes an axis and insets. The first example putsview()on three cards; the console shows aViewTimelineper card whose subject is the card. - 3
**
animation-rangecuts the journey.** The whole journey iscover.entryruns from the first pixel arriving until the subject is fully inside.exitruns from the first pixel leaving until it is gone.containruns while the subject is fully inside (or, if taller than the scrollport, while it covers it). Write a name (entry) or a pair (entry 0% entry 100%; the computed style shows plainentry), and names mix:entry 0% cover 40%. - 4
Two more ranges follow the subject's own size.
entry-crossingandexit-crossingdiffer fromentryandexitonly for a subject taller than the scrollport. In a 100 px scrollport (second example) a 40 px subject hasentryequal toentry-crossing(200 to 240 px of scroll); a 160 px subject hasentryfrom 200 to 300,entry-crossingfrom 200 to 360, andcontainfrom 300 to 360. - 5
Insets shrink the scrollport.
view(block 20px)(orview-timeline-inset) treats the scrollport as 20 px smaller at each edge, so the animation starts later and ends sooner. Measured in the third example: the plain timeline runs from 200 to 348 px of scroll, the inset one from 220 to 328. Use one for "not until the card is clear of the sticky header". - 6
An element in view at load starts part-way. In the third example the first box is 54% through its
coverjourney before any scrolling, so anentryanimation on it is already finished and it looks normal: a hero never waits. Before the journey starts, fill-modebothholds the first keyframe (opacity 0 in the first example). Animateopacityandtransformonly.
Explain it without notes
What is the difference between entry, contain and cover?
A card is already on screen when the page loads. What does its entry animation show, and why is that right?
Practice
Write the CSS for a feature card that fades and rises over entry, but only starts 40 px after it first touches the bottom of the screen.
A tall photo (500 px) in a 300 px box should fade in while it covers the box, never while half of it is out. Which range, and why?
Done when you can
You can write
animation-timeline: view()andanimation-rangefor a revealYou can say what entry, contain, exit and cover mean, and when the crossing ranges differ
You know what an inset does and what happens to an element in view at load
You choose the range so the effect ends before the reader needs the content