> value in 'animation-timeline'
and specifies a [=scroll progress timeline=].
Its syntax is
<> = scroll( [ <> || <> ]? )
<> = block | inline | x | y
<> = root | nearest | self
By default,
''scroll()'' references the [=block axis=] of the nearest ancestor [=block-axis scroll container=].
Its arguments modify this lookup as follows:
- block
-
Specifies to use the measure of progress along the
[=block axis=] of the [=block-axis scroll container=].
(Default.)
- inline
-
Specifies to use the measure of progress along the
[=inline axis=] of the [=inline-axis scroll container=].
- x
-
Specifies to use the measure of progress along the
[=horizontal axis=] of the [=x-axis scroll container=].
- y
-
Specifies to use the measure of progress along the
[=vertical axis=] of the [=y-axis scroll container=].
- nearest
-
Specifies to use the nearest ancestor [=scroll container=]
with an appropriate [=scrollable axis=]
(Default.)
- root
-
Specifies to use the document viewport as the [=scroll container=].
- self
-
Specifies to use the element’s own [=principal box=] as the [=scroll container=].
If the [=principal box=] is not a [=scroll container=] in the appropriate axis,
then the [=scroll progress timeline=] is [=inactive timeline|inactive=].
Note: Progress is in reference to the [=scroll origin=],
which can flip depending on [=writing mode=],
even when ''scroll-timeline-axis/x'' or ''scroll-timeline-axis/y'' is specified.
References to the [=root element=] propagate to the document viewport
(which functions as its [=scroll container=]).
Consider the following style sheet and markup:
```css
@keyframes change-background {
from { background-color: aliceblue; }
to { background-color: cornflowerblue; }
}
.subject {
animation: change-background linear both;
/* Use an anonymous scroll progress timeline to drive the animation */
animation-timeline: scroll();
}
```
```html
…
```
The `.subject`‘s animation is set up to be driven by
an anonymous [=Scroll progress timeline=] created with ''scroll()''.
Because no <
> or <> are passed into the function,
the default values of ''nearest'' and ''block'' respectively are used.
Because `.scroller` is the nearest ancestor [=scroll container=],
this results in the `.scroller` element driving the animation:
as you scroll `.scroller` up and down, the subject’s animations progress
moves forwards or backwards in direct response.
This results in the `background-color` being
`aliceblue` when at the start of the scroller
and `cornflowerblue` when scrolled to the very end.
Intermediary scroll positions results in an interpolated value.
Building on the previous example, changing the 'animation-timeline'
to the following results in
the document viewport driving the animation progress.
```css
.subject {
animation: change-background linear both;
animation-timeline: scroll(root);
}
```
The following declarations for 'animation-timeline' are all equivalent
but some are more explicit about what to target:
```css
/* These all are equivalent */
animation-timeline: scroll();
animation-timeline: scroll(block);
animation-timeline: scroll(nearest);
animation-timeline: scroll(block nearest);
animation-timeline: scroll(nearest block);
```
This is because the default values for <
> and <>
are ''nearest'' and ''block'' respectively.
To animate the scroller itself, use the ''self'' keyword as the <>
```css
@keyframes change-color {
from { color: black; }
to { color: hotpink; }
}
.subject {
animation: change-color linear both;
/* Use an anonymous scroll progress timeline to drive the animation */
animation-timeline: scroll(self);
}
```
Each use of ''scroll()'' corresponds to its own instance of {{ScrollTimeline}}
in the Web Animations API,
even if multiple elements use ''scroll()'' to refer
to the same [=scroll container=] with the same arguments.
### The {{ScrollTimeline}} Interface ### {#scrolltimeline-interface}
enum ScrollAxis {
"block",
"inline",
"x",
"y"
};
dictionary ScrollTimelineOptions {
Element? source;
ScrollAxis axis = "block";
};
[Exposed=Window]
interface ScrollTimeline : AnimationTimeline {
constructor(optional ScrollTimelineOptions options = {});
readonly attribute Element? source;
readonly attribute ScrollAxis axis;
};
A {{ScrollTimeline}} is an {{AnimationTimeline}}
that represents a [=scroll progress timeline=].
It can be passed to
the {{Animation}} constructor or the {{Animatable/animate()}} method
to link the animation to a [=scroll progress timeline=].
: source
:: The [=scroll container=] element
whose scroll position drives the progress of the timeline.
: axis
:: The axis of scrolling
that drives the progress of the timeline.
See value definitions for <>, above.
Inherited attributes:
: {{AnimationTimeline/currentTime}} (inherited from {{AnimationTimeline}})
:: Represents the scroll progress of the [=scroll container=]
as a percentage CSSUnitValue,
with 0% representing its startmost scroll position
(in the [=writing mode=] of the [=scroll container=]).
Null when the timeline is [=inactive timeline|inactive=].
ISSUE: While 0% will usually represent the [=scroll container=]’s initial scroll position,
it might not depending on its [=content distribution=].
See [[css-align-3#overflow-scroll-position]].
Is this what we want?
ISSUE: Add a note about whether {{AnimationTimeline/currentTime}}
can be negative or > 100%.
: ScrollTimeline(options)
:: Creates a new {{ScrollTimeline}} object using the following procedure:
1. Let |timeline| be the new {{ScrollTimeline}} object.
1. Set the {{ScrollTimeline/source}} of |timeline| to:
: If the `source` member of |options| is present,
:: The `source` member of |options|.
: Otherwise,
:: The {{Document/scrollingElement}}
of the {{Document}} associated
with the {{Window}} that is the current global object.
1. Set the {{ScrollTimeline/axis}} property of |timeline|
to the corresponding value from |options|.
If the {{ScrollTimeline/source}} of a {{ScrollTimeline}}
is an element whose [=principal box=] does not exist
or is not a [=scroll container=] in the timeline's axis,
or if there is no [=scrollable overflow=],
then the {{ScrollTimeline}} is [=inactive timeline|inactive=].
A {{ScrollTimeline}}’s {{AnimationTimeline/duration}} is 100%.
The values of {{ScrollTimeline/source}} and {{AnimationTimeline/currentTime}}
are both computed when either is requested or updated.
In the following example, a {{ScrollTimeline}} instance
that tracks the document viewport in the block direction
is created:
```js
const myTimeline = new ScrollTimeline({
source: document.documentElement,
});
```
The created {{ScrollTimeline}} instance can be used
to animate an element as follows:
```js
const progressbar = document.querySelector('#progress');
progressbar.animate(
{
transform: ['scaleX(0)', 'scaleX(1)'],
},
{
fill: 'forwards',
timeline: myTimeline,
}
);
```
This makes the targeted `#progress` element
animate from a `scaleX(0)` transform when at the top of the page
to a `scaleX(1)` when at the bottom of the page.
In this example, the `.scroller` element is the element
whose scroll position drives the progress of the timeline.
The timeline is set to track its scroll offset in the inline direction.
```js
const scroller = document.querySelector('.scroller');
const myTimeline = new ScrollTimeline({
source: scroller,
axis: 'inline',
});
```
Scrolling the root scroller has no effect here,
it is only when you scroll the `.scroller` element in the inline direcion
that the animation that uses the timeline will tick.
## Named Scroll Progress Timelines ## {#scroll-timelines-named}
[=Scroll progress timelines=] can also be defined on the [=scroll container=] itself,
and then referenced by name
by elements within the name’s scope
(see [[#timeline-scoping]]).
Such named scroll progress timelines
are declared in the [=coordinated value list=]
constructed from the [=longhands=] of the 'scroll-timeline' [=shorthand property=],
which form a [=coordinating list property group=]
with 'scroll-timeline-name' as the [=coordinating list base property=].
See [[css-values-4#linked-properties]].
In the following example the `.subject`’s animation is driven by
the [=named scroll progress timeline=] named `--my-scroller`.
This timeline is created on its `.scroller` ancestor and is set up to
measure progress along the [=inline axis=]:
```css
.scroller {
scroll-timeline-name: --my-scroller;
scroll-timeline-axis: inline;
}
.scroller .subject {
animation: grow linear both;
/* Use the '--my-scroller' scroll progress timeline to drive the animation */
animation-timeline: --my-scroller;
}
```
As you scroll horizontally through the `.scroller` element,
the `grow` animation on the contained `.subject` element
will move forwards or backwards in direct response.
### Naming a Scroll Progress Timeline: the 'scroll-timeline-name' property ### {#scroll-timeline-name}
Name: scroll-timeline-name
Value: [ none | <> ]#
Initial: none
Applies to: all elements
Inherited: no
Computed value: list, each item either a [=CSS identifier=] or the keyword ''scroll-timeline-name/none''
Animation type: not animatable
Specifies names for the [=named scroll progress timelines=]
associated with this element.
### Axis of a Scroll Progress Timeline: the 'scroll-timeline-axis' property ### {#scroll-timeline-axis}
Name: scroll-timeline-axis
Value: [ block | inline | x | y ]#
Initial: block
Applies to: all elements
Inherited: no
Computed value: a list of the keywords specified
Animation type: not animatable
Specifies the axis of any [=named scroll progress timelines=]
sourced from this [=scroll container=].
If this box is not a [=scroll container=] in the specified axis,
then the corresponding [=named scroll progress timeline=]
is [=inactive timeline|inactive=].
Values are as defined for ''scroll()''.
### Scroll Timeline Shorthand: the 'scroll-timeline' shorthand ### {#scroll-timeline-shorthand}
Name: scroll-timeline
Value: [ <<'scroll-timeline-name'>> <<'scroll-timeline-axis'>>? ]#
Applies to: all elements
Inherited: no
Animation type: not animatable
This property is a [=shorthand=] for setting
'scroll-timeline-name' and 'scroll-timeline-axis'
in a single declaration.
The following two rules are equivalent:
```css
.scroller {
scroll-timeline-name: --my-scroller;
scroll-timeline-axis: inline;
}
```
```css
.scroller {
scroll-timeline: --my-scroller inline;
}
```
# View Progress Timelines # {#view-timelines}
Often animations are desired to start and end
during the portion of the [=scroll progress timeline=]
that a particular box
(the view progress subject)
is in view within the [=scrollport=].
View progress timelines
are segments of a [=scroll progress timeline=]
that are scoped to the scroll positions
in which any part of the subject element’s [=principal box=]
intersects its nearest ancestor [=scrollport=]
(or more precisely, the relevant [=view progress visibility range=]
of that [=scrollport=]).
The startmost such scroll position represents 0% progress,
and the endmost such scroll position represents 100% progress;
see [[#view-timeline-progress]].
Note: The 0% and 100% scroll positions are not always reachable,
e.g. if the box is positioned
at the start edge of the [=scrollable overflow rectangle=],
it might not be possible to scroll to < 32% progress.
[=View progress timelines=] can be referenced
anonymously using the ''view()'' [=functional notation=]
or by name (see [[#timeline-scoping]])
after declaring them using the 'view-timeline' properties
on the [=view progress subject=].
In the Web Animations API,
they can be represented anonymously by a {{ViewTimeline}} object.
## View Progress Timeline Ranges ## {#view-timelines-ranges}
[=View progress timelines=] define the following [=named timeline ranges=]:
- cover
-
Represents the full range of the [=view progress timeline=]:
* 0% progress represents the latest position at which
the [=start=] [=border edge=] of the element’s [=principal box=]
coincides with the [=end=] edge of its [=view progress visibility range=].
* 100% progress represents the earliest position at which
the [=end=] [=border edge=] of the element’s [=principal box=]
coincides with the [=start=] edge of its [=view progress visibility range=].
- contain
-
Represents the range during which the [=principal box=]
is either fully contained by, or fully covers,
its [=view progress visibility range=] within the [=scrollport=].
* 0% progress represents the earliest position at which either:
* the [=start=] [=border edge=] of the element’s [=principal box=]
coincides with the [=start=] edge of its [=view progress visibility range=].
* the [=end=] [=border edge=] of the element’s [=principal box=]
coincides with the [=end=] edge of its [=view progress visibility range=].
* 100% progress represents the latest position at which either:
* the [=start=] [=border edge=] of the element’s [=principal box=]
coincides with the [=start=] edge of its [=view progress visibility range=].
* the [=end=] [=border edge=] of the element’s [=principal box=]
coincides with the [=end=] edge of its [=view progress visibility range=].
- entry
-
Represents the range during which the [=principal box=]
is entering the [=view progress visibility range=].
* 0% is equivalent to 0% of the ''animation-timeline-range/cover'' range.
* 100% is equivalent to 0% of the ''animation-timeline-range/contain'' range.
- exit
-
Represents the range during which the [=principal box=]
is exiting the [=view progress visibility range=].
* 0% is equivalent to 100% of the ''animation-timeline-range/contain'' range.
* 100% is equivalent to 100% of the ''animation-timeline-range/cover'' range.
- entry-crossing
-
Represents the range during which the [=principal box=] crosses the [=end=]
[=border edge=]
* 0% progress represents the latest position at which
the [=start=] [=border edge=] of the element’s [=principal box=]
coincides with the [=end=] edge of its [=view progress visibility range=].
* 100% progress represents the earliest position at which
the [=end=] [=border edge=] of the element’s [=principal box=]
coincides with the [=end=] edge of its [=view progress visibility range=].
- exit-crossing
-
Represents the range during which the [=principal box=] crosses the [=start=]
[=border edge=]
* 0% progress represents the latest position at which
the [=start=] [=border edge=] of the element’s [=principal box=]
coincides with the [=start=] edge of its [=view progress visibility range=].
* 100% progress represents the earliest position at which
the [=end=] [=border edge=] of the element’s [=principal box=]
coincides with the [=start=] edge of its [=view progress visibility range=].
- scroll
-
Represents the full range of the [=scroll container=] on which the [=view progress timeline=]
is defined.
* 0% progress represents the startmost scroll position of the [=scroll container=]
underlying the [=view progress timeline=].
* 100% progress represents the endmost scroll position of the [=scroll container=]
underlying the [=view progress timeline=].
ISSUE: Insert diagrams.
In all cases, the [=writing mode=] used to resolve the [=start=] and [=end=] sides
is the [=writing mode=] of the relevant [=scroll container=].
Transforms are ignored,
but [=relative positioning|relative=] and [=absolute positioning|absolute=] positioning
are accounted for.
Note: For [=sticky-positioned boxes=]
the 0% and 100% progress conditions can sometimes be satisfied
by a range of scroll positions rather than just one.
Each range therefore indicates whether to use
the earliest or latest qualifying position.
[[CSS-POSITION-3]] [[CSS-TRANSFORMS-1]]
## Calculating Progress for a View Progress Timeline ## {#view-timeline-progress}
Progress (the [=timeline/current time=]) in a [=view progress timeline=]
is calculated as:
|distance| ÷ |range|
where:
* |distance| is the current [=scroll offset=]
minus the [=scroll offset=] corresponding to
the start of the ''animation-timeline-range/cover'' range
* |range| is
the [=scroll offset=] corresponding to
the end of the ''animation-timeline-range/cover'' range
minus
the [=scroll offset=] corresponding to
the start of the ''animation-timeline-range/cover'' range
If the 0% position and 100% position coincide
(i.e. the denominator in the [=timeline/current time=] formula is zero),
the timeline is [=inactive timeline|inactive=].
In [=paged media=],
[=view progress timelines=] that would otherwise reference the document viewport
are also [=inactive timeline|inactive=].
## Anonymous View Progress Timelines ## {#view-timelines-anonymous}
### The ''view()'' notation ### {#view-notation}
The view() functional notation
can be used as a <> value in 'animation-timeline'
and specifies a [=view progress timeline=]
in reference to the nearest ancestor [=scroll container=]
that's scrollable in the specified axis.
Its syntax is
<> = view( [ <> || <<'view-timeline-inset'>> ]? )
By default,
''view()'' references the [=block axis=];
as for ''scroll()'',
this can be changed by providing an explicit <> value.
The optional <<'view-timeline-inset'>> value provides an adjustment
of the [=view progress visibility range=],
as defined for 'view-timeline-inset'.
In the following example, each direct child of the `.scroller` element
will reveal itself as it crosses the scrollport.
```css
@keyframes reveal {
from { opacity: 0; }
}
.scroller > * {
animation: reveal linear both;
animation-timeline: view();
}
```
For every element matched by the selector,
a [=view progress timeline=] gets created to drive the animation.
Because no arguments are passed into ''view()'' it uses the default values
for <
> and <<'view-timeline-inset'>>,
thus tracking its scroll position in the [=block axis=].
With the keyframes driven by the [=view progress timeline=],
the element will be at `opacity: 0` when it is about to enter the scrollport,
and at `opacity: 1` when it has just left the scrollport.
Any scroll position in between results in an interpolated value.
Note: Because matched elements
can be positioned at different offsets within the scroller
or can differ in size,
every matched element gets its own unique [=view progress timeline=].
Each use of ''view()'' corresponds to its own instance of {{ViewTimeline}}
in the Web Animations API,
even if multiple elements use ''view()'' to reference
the same element with the same arguments.
### The {{ViewTimeline}} Interface ### {#viewtimeline-interface}
dictionary ViewTimelineOptions {
required Element subject;
ScrollAxis axis = "block";
(DOMString or sequence<(CSSNumericValue or CSSKeywordValue)>) inset = "auto";
};
[Exposed=Window]
interface ViewTimeline : ScrollTimeline {
constructor(ViewTimelineOptions options);
readonly attribute Element subject;
readonly attribute CSSNumericValue startOffset;
readonly attribute CSSNumericValue endOffset;
};
A {{ViewTimeline}} is an {{AnimationTimeline}}
that specifies a [=view progress timeline=].
It can be passed to
the {{Animation}} constructor or the {{Animatable/animate()}} method
to link the animation to a [=view progress timeline=].
: subject
:: The element whose [=principal box=]’s visibility in the [=scrollport=]
defines the progress of the timeline.
: startOffset
:: Represents the starting (0% progress) scroll position
of the [=view progress timeline=]
as a length offset (in ''px'') from the [=scroll origin=].
Null when the timeline is [=inactive timeline|inactive=].
: endOffset
:: Represents the ending (100% progress) scroll position
of the [=view progress timeline=]
as a length offset (in ''px'') from the [=scroll origin=].
Null when the timeline is [=inactive timeline|inactive=].
Note: The values of {{ViewTimeline/startOffset}} and {{ViewTimeline/endOffset}}
are relative to the [=scroll origin=], not the [=physical=] top left corner.
Depending on the [=writing mode=] of the [=scroll container=],
they therefore might not match {{Element/scrollLeft}} or {{Element/scrollTop}} values,
for example in the [=horizontal axis=] in a right-to-left (''rtl'') [=writing mode=].
Inherited attributes:
: {{ScrollTimeline/source}} (inherited from {{ScrollTimeline}})
:: The nearest ancestor of the {{ViewTimeline/subject}}
whose [=principal box=] establishes a [=scroll container=]
in the specified axis,
whose scroll position drives the progress of the timeline.
: {{ScrollTimeline/axis}} (inherited from {{ScrollTimeline}})
:: Specifies the axis of scrolling
that drives the progress of the timeline.
See <>, above.
: {{AnimationTimeline/currentTime}} (inherited from {{AnimationTimeline}})
:: Represents the current progress
of the [=view progress timeline=]
as a percentage {{CSSUnitValue}}
representing its [=scroll container=]’s scroll progress at that position.
Null when the timeline is [=inactive timeline|inactive=].
: ViewTimeline(options)
:: Creates a new {{ViewTimeline}} object using the following procedure:
1. Let |timeline| be the new {{ViewTimeline}} object.
1. Set the {{ViewTimeline/subject}} and {{ScrollTimeline/axis}} properties of |timeline|
to the corresponding values from |options|.
1. Set the {{ScrollTimeline/source}} of |timeline|
to the {{ViewTimeline/subject}}’s
nearest ancestor [=scroll container=] element
that's scrollable in the specified axis.
1. If a {{DOMString}} value is provided as an inset,
parse it as a <<'view-timeline-inset'>> value;
if a sequence is provided,
the first value represents the start inset
and the second value represents the end inset.
If the sequence has only one value,
it is duplicated.
If it has zero values or more than two values,
or if it contains a {{CSSKeywordValue}} whose {{CSSKeywordValue/value}} is not "auto",
throw a TypeError.
These insets define the {{ViewTimeline}}’s [=view progress visibility range=].
If the {{ScrollTimeline/source}} or {{ViewTimeline/subject}} of a {{ViewTimeline}}
is an element whose [=principal box=] does not exist,
or if its nearest ancestor [=scroll container=] has no [=scrollable overflow=]
or is not scrollable in the specified axis
(or if there is no such ancestor, e.g. in print media),
then the {{ViewTimeline}} is [=inactive timeline|inactive=].
The values of {{ViewTimeline/subject}}, {{ScrollTimeline/source}}, and {{AnimationTimeline/currentTime}}
are all computed when any of them is requested or updated.
In the following example, each child of the `.scroller` element
will reveal itself as it crosses the scrollport.
```js
document.querySelectorAll('.scroller > *').forEach(childElement => {
const timeline = new ViewTimeline({
subject: childElement,
axis: 'block',
});
childElement.animate({
opacity: [ 0, 1 ],
}, {
fill: 'both',
timeline,
});
});
```
In a vertical scroller, this results in an element going fully transparent (`opacity: 0`)
when its about to enter the scroller from the bottom edge of the scroller
to being fully opaque (`opacity: 1`) when it has completely entered the scroller.
Scroll positions in between result in an opacity `0` and `1`.
## Named View Progress Timelines ## {#view-timelines-named}
[=View progress timelines=] can also be defined declaratively
and then referenced by name
by elements within the name’s scope
(see [[#timeline-scoping]]).
Such named view progress timelines
are declared in the [=coordinated value list=]
constructed from the 'view-timeline-*' properties,
which form a [=coordinating list property group=]
with 'view-timeline-name' as the [=coordinating list base property=].
See [[css-values-4#linked-properties]].
This example behaves exactly the same
as the previous one:
each child of the `.scroller` element will reveal itself
as it crosses the scrollport.
The difference is that
instead of using an
anonymous [=View progress timeline=]
to drive the animation, it now uses a [=named view progress timeline=] that tracks the `.scroller`.
```css
@keyframes reveal {
from { opacity: 0; }
}
.scroller {
view-timeline: --my-scroller block;
}
.scroller > * {
animation: reveal linear both;
animation-timeline: --my-scroller;
}
```
### Naming a View Progress Timeline: the 'view-timeline-name' property ### {#view-timeline-name}
Name: view-timeline-name
Value: [ none | <> ]#
Initial: none
Applies to: all elements
Inherited: no
Computed value: list, each item either a [=CSS identifier=] or the keyword ''view-timeline-name/none''
Animation type: not animatable
Specifies names for the [=named view progress timelines=]
associated with this element.
### Axis of a View Progress Timeline: the 'view-timeline-axis' property ### {#view-timeline-axis}
Name: view-timeline-axis
Value: [ block | inline | x | y ]#
Initial: block
Applies to: all elements
Inherited: no
Computed value: a list of the keywords specified
Animation type: not animatable
Specifies the axis of any [=named view progress timelines=]
derived from this element’s [=principal box=].
Values are as defined for ''view()''.
### Inset of a View Progress Timeline: the 'view-timeline-inset' property ### {#view-timeline-inset}
Name: view-timeline-inset
Value: [ [ auto | <> ]{1,2} ]#
Initial: auto
Applies to: all elements
Inherited: no
Percentages: relative to the corresponding dimension of the relevant scrollport
Computed value: a list consisting of two-value pairs representing the start and end insets each as either the keyword ''view-timeline-inset/auto'' or a computed <> value
Animation type: by computed value type
Specifies an inset (positive) or outset (negative) adjustment of the [=scrollport=]
when determining whether the box is in view
when setting the bounds of the corresponding [=view progress timeline=].
The first value represents the [=start=] inset in the relevant axis;
the second value represents the [=end=] inset.
If the second value is omitted, it is set to the first.
The resulting range of the [=scrollport=] is
the view progress visibility range.
- auto
-
Indicates to use the value of 'scroll-padding'.
- <>
-
Like 'scroll-padding',
defines an inward offset from the corresponding edge of the scrollport.
### View Timeline Shorthand: the 'view-timeline' shorthand ### {#view-timeline-shorthand}
Name: view-timeline
Value: [ <<'view-timeline-name'>> [ <<'view-timeline-axis'>> || <<'view-timeline-inset'>> ]? ]#
Applies to: all elements
This property is a [=shorthand=] for setting
'view-timeline-name', 'view-timeline-axis', and 'view-timeline-inset'
in a single declaration.
# Attaching Animations to [=Scroll-driven Timelines=] # {#scroll-driven-attachment}
Animations can be attached to [=scroll-driven timelines=]
using the 'animation-timeline' property (in CSS)
or the {{AnimationTimeline}} parameters (in the Web Animations API).
The timeline range to which their [=active interval=] is attached
can also be further restricted to a particular timeline range
(see [[#named-range-animation-declaration]]).
Time-based delays ('animation-delay')
get converted to their respective proportions
as described in [[web-animations-2#animations]]
## Finite Timeline Calculations ## {#finite-attachment}
Unlike time-driven timelines,
[=scroll-driven timelines=] are finite,
thus [=scroll-driven animations=] are always attached
to a finite [=attachment range=]--
which may be further limited by 'animation-range'
(see [[#timeline-ranges]]).
The animation’s
iterations ('animation-iteration-count')
are set within the limits of this finite range.
If the specified duration is ''animation-duration/auto'',
then
the remaining range is divided by its [=iteration count=] ('animation-iteration-count')
to find the [=used value|used=] duration.
Note: If the animation has an infinite [=iteration count=],
each [=iteration duration=]--
and the resulting [=active duration=]--
will be zero.
Animations that include absolutely-positioned keyframes
(those pinned to a specific point on the timeline,
e.g. using [[#named-range-keyframes|named timeline range keyframe selectors]] in ''@keyframes'')
are assumed to have an [=iteration count=] of 1
for the purpose of finding those keyframes’ positions
relative to 0% and 100%;
the entire animation is then scaled to fit the [=iteration duration=]
and repeated for each iteration.
Note: It's unclear what the use case might be
for combining absolutely-positioned keyframes
with iteration counts above 1;
this at least gives a defined behavior.
(An alternative, but perhaps weirder, behavior
would be to take such absolutely-positioned keyframes
“out of flow” while iterating the remaining keyframes.)
The editors would be interested in hearing about
any real use cases for multiple iterations here.
## Named Timeline Scoping and Lookup ## {#timeline-scoping}
Timeline names are global by default:
An element referring to a named [=scroll progress timeline=] or [=view progress timeline=]
can find a [=target timeline=] defined on itself,
defined on an element *before* itself in tree order,
or defined on an element *after* itself in tree order.
The
target timeline
for a given element |element|
referencing a timeline name |name| is:
: If |element| defines a timeline matching |name|,
:: That timeline.
: Otherwise,
if |name| is [[#timeline-scope|scoped]] by |element|,
or if |element| is the [=document element=],
:: The last defined timeline matching |name|
among |element|'s [=flat tree=] descendants
in [=flat tree=] order,
or, if no such timeline exists, an [=inactive timeline=].
: Otherwise,
:: The [=target timeline=]
for |element|'s [=flat tree=] parent referencing |name|.
An element may only define one timeline per name:
In case of a name conflict,
names declared later in the naming property
('scroll-timeline-name', 'view-timeline-name')
take precedence, and
[=scroll progress timelines=] take precedence over [=view progress timelines=].
The element `#foo` defines a single [=scroll progress timeline=]
in the block direction named `--t`,
and no [=view progress timeline=] at all.
```css
#foo {
scroll-timeline: --t inline, --t block;
view-timeline: --t;
}
```
An element can refer to timelines
bound to elements that are siblings, cousins, or even descendants.
For example, the following creates an animation on an element
that is linked to a [=scroll progress timeline=]
defined by the subsequent sibling.
…
Animating Box
Scrollable Box
## Animation Events ## {#events}
[=Scroll-driven animations=] dispatch all the same animation events
as the more typical time-driven animations
as described in
[[web-animations-1#animation-events-section]],
[[css-animations-1#events]],
and [[css-animations-2#event-dispatch]].
Note: When scrolling backwards,
the animationstart event will fire
at the end of the [=active interval=],
and the animationend event will fire
at the start of the [=active interval=].
However, since the finish event
is about entering the [=play state/finished|finished play state=],
it only fires when scrolling forwards.
# Frame Calculation Details # {#frames}
## HTML Processing Model: Event loop ## {#event-loop}
The ability for scrolling to drive the progress of an animation,
gives rise to the possibility of layout cycles,
where a change to a scroll offset causes an animation's effect to update,
which in turn causes a new change to the scroll offset.
To avoid such [=layout cycles=],
animations with a [=scroll progress timeline=] update their current time once
during step 7.10 of the [HTML Processing Model](https://html.spec.whatwg.org/multipage/webappapis.html#processing-model-8) event loop,
as step 1 of [=update animations and send events=].
During step 7.14.1 of the [HTML Processing Model](https://html.spec.whatwg.org/multipage/webappapis.html#processing-model-8),
any created [=scroll progress timelines=] or [=view progress timelines=] are collected into a stale timelines set.
After step 7.14 if any timelines' [=named timeline ranges=] have changed,
these timelines are added to the [=stale timelines=] set.
If there are any [=stale timelines=], they now update their current time and associated ranges,
the set of [=stale timelines=] is cleared and
we run an additional step to recalculate styles and update layout.
Note: We check for layout changes after dispatching any {{ResizeObserver}}s intentionally
to take programmatically sized elements into account.
Note: As we only gather stale timelines during the first style and layout calculation,
this can only directly cause one additional style recalculation.
Other APIs which require another update should be checked in the same step
and be updated at the same time.
Note: Without this additional round of style and layout,
[=stale timelines|initially stale=] timelines would remain stale
(i.e. they would not have a current time)
for the remainder of the frame where the timeline was created.
This means that animations linked to such a timeline
would not produce any [=effect value=] for that frame,
which could lead to an undesirable initial "flash"
in the rendered output.
Note: This section has no effect on forced style and layout
calculations triggered by {{Window/getComputedStyle()|getComputedStyle()}} or similar.
In other words, [=stale timelines|initially stale=] timelines are visible as such
through those APIs.
If the final style and layout update
would result in a change in the time or scope (see 'timeline-scope')
of any [=scroll progress timelines=] or [=view progress timelines=],
they will not be re-sampled to reflect the new state
until the next update of the rendering.
Nothing in this section is intended to require
that scrolling block on layout or script.
If a user agent normally composites frames where scrolling has occurred
but the consequences of scrolling have not been fully propagated in layout or script
(for example, `scroll` event listeners have not yet run),
the user agent may likewise choose not to sample scroll-driven animations
for that composited frame.
In such cases, the rendered scroll offset
and the state of a scroll-driven animation
may be inconsistent in the composited frame.
When updating timeline current time,
the [=start time=] of any attached animation is conditionally updated.
For each attached animation,
run the procedure for [=calculating an auto-aligned start time=].
# Privacy Considerations # {#privacy-considerations}
There are no known privacy impacts of the features in this specification.
# Security Considerations # {#security-considerations}
There are no known security impacts of the features in this specification.
# Appendix A: Timeline Ranges # {#timeline-ranges}
ISSUE: This section should move to CSS-ANIMATIONS-2 and WEB-ANIMATIONS-2.
This appendix introduces the concepts of [=named timeline ranges=]
and [=animation attachment ranges=]
to CSS Animations
and Web Animations.
## Named Timeline Ranges ## {#named-ranges}
A named timeline range
is a named segment of an animation [=timeline=].
The start of the segment is represented as 0% progress through the range;
the end of the segment is represented as 100% progress through the range.
Multiple [=named timeline ranges=] can be associated with a given [=timeline=],
and multiple such ranges can overlap.
For example, the ''animation-timeline/contain'' range of a [=view progress timeline=]
overlaps with its ''animation-timeline/cover'' range.
[=Named timeline ranges=] are represented by
the <> value type,
which indicates a [=CSS identifier=] representing
one of the predefined [=named timeline ranges=].
Note: In this specification, [=named timeline ranges=]
must be defined to exist by a specification
such as [[SCROLL-ANIMATIONS-1]].
A future level may introduce APIs for authors to declare
their own custom [=named timeline ranges=].
## Named Timeline Range Keyframe Selectors ## {#named-range-keyframes}
[=Named timeline range=] names and percentages
can be used to attach keyframes
to specific progress points within the [=named timeline range=].
The CSS ''@keyframes'' rule is extended thus:
<> = from | to | <> | <> <>
where <> is the [=CSS identifier=]
that represents a chosen predefined [=named timeline range=],
and the <> after it represents the percentage progress
between the start and end of that [=named timeline range=].
Keyframes are attached to the specified point in the timeline.
If the timeline does not have a corresponding [=named timeline range=],
then any keyframes attached to points on that [=named timeline range=]
are ignored.
It is possible that these attachment points
are outside the [=active interval=] of the animation;
in these cases the automatic ''from'' (0%) and ''to'' (100%) keyframes
are only generated for properties that don't have keyframes
at or earlier than 0% or at or after 100% (respectively).
In this example the range information is included directly in the ''@keyframes'' rule:
```css
@keyframes animate-in-and-out {
entry 0% {
opacity: 0; transform: translateY(100%);
}
entry 100% {
opacity: 1; transform: translateY(0);
}
exit 0% {
opacity: 1; transform: translateY(0);
}
exit 100% {
opacity: 0; transform: translateY(-100%);
}
}
.scroller > * {
animation: linear animate-in-and-out;
animation-timeline: view();
}
```
It has the same outcome as the following snippet which uses
two distinct sets of keyframes combined with 'animation-range'
```css
@keyframes animate-in {
0% { opacity: 0; transform: translateY(100%); }
100% { opacity: 1; transform: translateY(0); }
}
@keyframes animate-out {
0% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-100%); }
}
.scroller > * {
animation: animate-in linear forwards,
animate-out linear forwards;
animation-timeline: view();
animation-range: entry, exit;
}
```
## Attaching Animations to Timeline Ranges ## {#named-range-animation-declaration}
A set of animation keyframes can be attached
in reference to an animation attachment range,
restricting the animation’s [=active interval=] to that range of a timeline,
with the 'animation-range' properties.
Delays (see 'animation-delay')
are set within this restricted range,
further reducing the time available for ''animation-duration/auto'' durations
and ''animation-iteration-count/infinite'' iterations.
Note: 'animation-range' can expand the [=attachment range=]
as well as constrict it.
Any frames positioned outside the [=attachment range=]
are used for interpolation as needed,
but are outside the [=active interval=]
and therefore dropped from the animation itself,
effectively truncating the animation
at the end of its [=attachment range=].
range start┐ ╺┉┉active interval┉┉╸ ┌range end
┄┄┄┄┄┄┄┄┄┄┄├─────────────╊━━━━━━━━━━━━━━━━━━━╉───────────┤┄┄┄┄┄┄┄┄
╶┄start delay┄╴ ╶┄end delay┄╴
╶┄┄┄┄┄ duration┄┄┄┄┄╴
The 'animation-range' properties
are [=reset-only sub-properties=] of the 'animation' [=shorthand=].
These list-valued properties
form a [=coordinating list property group=]
with 'animation-name' as the [=coordinating list base property=].
ISSUE: Define application to time-driven animations.
### Examples ### {#animation-range-examples}
In the following example, each direct child of the `.scroller` element
reveals itself as it enters the scrollport instead of when entirely crossing it.
This is achieved by setting 'animation-range' to limit the [=active interval=]
to the ''entry'' range instead of the default ''cover'' range:
```css
@keyframes reveal {
from { opacity: 0; }
}
.scroller > * {
animation: reveal linear both;
animation-timeline: view();
animation-range: entry;
}
```
In a vertical scroller, this results in an element going fully transparent (`opacity: 0`)
when its about to enter the scroller from the bottom edge of the scroller
to being fully opaque (`opacity: 1`) when it has completely entered the scroller.
Scroll positions in between result in an opacity `0` and `1`.
A variation of the previous example is to add both entry and exit effects,
each linked to their own 'animation-range':
```css
@keyframes reveal {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes hide {
from { opacity: 1; }
to { opacity: 0; }
}
.scroller > * {
animation: reveal linear both,
hide linear forwards;
animation-timeline: view();
animation-range: entry, exit;
}
```
The `reveal` effect is linked to the ''entry'' range
while the `hide` effect is linked to the ''exit'' range.
In a vertical scroller, when scrolling down,
this results in the element going from `opacity: 0` to `opacity: 1`
as it enters the scrollport from the bottom edge of the scroller.
When continuing to scroll down,
the element will eventually go from `opacity: 1` to `opacity: 0`
as the subject exits the scrollport at the top edge of the scroller.
The Web Animations API equivalent of the previous example is the following:
```js
document.querySelectorAll('.scroller > *').forEach(childElement => {
const timeline = new ViewTimeline({
subject: childElement,
axis: 'block',
});
// Reveal effect on entry
childElement.animate({
opacity: [ 0, 1 ],
}, {
fill: 'forwards',
timeline,
rangeStart: 'entry 0%',
rangeEnd: 'entry 100%',
});
// Hide effect on exit
childElement.animate({
opacity: [ 1, 0 ],
}, {
fill: 'forwards',
timeline,
rangeStart: 'exit 100%',
rangeEnd: 'exit 100%',
});
});
```
For every matched element, a single timeline tracking that element is created.
The timeline tracks the element inside its scroller in the block direction.
The timeline is used to drive two animations added to the element
but the effects are only attached to a part of the range,
thanks to the `rangeStart` and `rangeEnd` options.
The first animation is attached to the ''entry'' range,
animating the element from `opacity: 0` to `opacity: 1`
as it enters the scrollport.
The second animation is attached to the ''exit'' range,
animating the element from `opacity: 1` to `opacity: 0`
as it leaves the scrollport.
### Specifying an Animation’s Timeline Range: the 'animation-range' shorthand ### {#animation-range}
Name: animation-range
Value: [ <<'animation-range-start'>> <<'animation-range-end'>>? ]#
The 'animation-range' property is a [=shorthand property|shorthand=]
that sets 'animation-range-start' and 'animation-range-end'
together in a single declaration,
associating the animation with the specified [=animation attachment range=].
If <<'animation-range-end'>> is omitted
and <<'animation-range-start'>> includes a <> component,
then 'animation-range-end' is set to that same <> and ''100%''.
Otherwise, any omitted [=longhand=] is set to its [=initial value=].
The following sets of declarations show an 'animation-range' [=shorthand=] declaration
followed by its equivalent 'animation-range-start' and 'animation-range-end' declarations:
animation-range: entry 10% exit 90%;
animation-range-start: entry 10%;
animation-range-end: exit 90%;
animation-range: entry;
animation-range-start: entry 0%;
animation-range-end: entry 100%;
animation-range: entry exit;
animation-range-start: entry 0%;
animation-range-end: exit 100%;
animation-range: 10%;
animation-range-start: 10%;
animation-range-end: normal;
animation-range: 10% 90%;
animation-range-start: 10%;
animation-range-end: 90%;
animation-range: entry 10% exit;
animation-range-start: entry 10%;
animation-range-end: exit 100%;
animation-range: 10% exit 90%;
animation-range-start: 10%;
animation-range-end: exit 90%;
animation-range: entry 10% 90%;
animation-range-start: entry 10%;
animation-range-end: 90%;
Because <<'animation-range-start'>> and <<'animation-range-end'>> accept <>s,
it’s perfectly fine to do calculations using ''calc()'' for the ranges:
```css
#subject {
animation: anim linear both;
animation-timeline: view();
animation-range: entry calc(100% - 100px) exit calc(0% + 100px);
}
```
ISSUE(8438): What's the best way to handle defaulting of omitted values here?
### Specifying an Animation’s Timeline Range Start: the 'animation-range-start' property ### {#animation-range-start}
Name: animation-range-start
Value: [ normal | <> | <> <>? ]#
Initial: normal
Applies to: all elements
Inherited: no
Percentages: relative to the specified [=named timeline range=] if one was specified, else to the entire timeline
Computed value: list, each item either the keyword ''animation-range-start/normal'' or a timeline range and progress percentage
Animation type: not animatable
Specifies the start of the animations’s [=attachment range=],
shifting the start time of the animation
(i.e. where keyframes mapped to 0% progress are attached
when the iteration count is 1)
accordingly.
Values have the following meanings:
- normal
-
The start of the animation’s [=attachment range=]
is the start of its associated [=timeline=];
the start of the animation’s [=active interval=]
is determined as normal.
- <>
-
The [=animation attachment range=] starts
at the specified point on the [=timeline=]
measuring from the start of the timeline.
- <> <>?
-
The [=animation attachment range=] starts
at the specified point on the [=timeline=]
measuring from the start of the specified [=named timeline range=].
If the <> is omitted,
it defaults to 0%.
### Specifying an Animation’s Timeline Range End: the 'animation-range-end' property ### {#animation-range-end}
Name: animation-range-end
Value: [ normal | <> | <> <>? ]#
Initial: normal
Applies to: all elements
Inherited: no
Percentages: relative to the specified [=named timeline range=] if one was specified, else to the entire timeline
Computed value: list, each item either the keyword ''animation-range-end/normal'' or a timeline range and progress percentage
Animation type: not animatable
Specifies the end of the animations’s [=attachment range=],
potentially shifting the end time of the animation
(i.e. where keyframes mapped to 100% progress are attached
when the iteration count is 1)
and/or truncating the animation’s [=active interval=].
Values have the following meanings:
- normal
-
The end of the animation’s [=attachment range=]
is the end of its associated [=timeline=];
the end of the animation’s [=active interval=]
is determined as normal.
- <>
-
The [=animation attachment range=] ends
at the specified point on the [=timeline=]
measuring from the start of the timeline.
- <> <>?
-
The [=animation attachment range=] ends
at the specified point on the [=timeline=]
measuring from the start of the specified [=named timeline range=].
If the <> is omitted,
it defaults to 100%.
# Appendix B: Timeline Name Scope # {#timeline-name-scope}
ISSUE: This section should move to CSS-ANIMATIONS-2.
This appendix introduces the 'timeline-scope' property,
which allows declaring a timeline name’s scope on an ancestor
of the timeline’s defining element.
## Declaring a Named Timeline’s Scope: the 'timeline-scope' property ## {#timeline-scope}
Name: timeline-scope
Value: none | all | <>#
Initial: none
Applies to: all elements
Inherited: no
Computed value: the keyword ''timeline-scope/none'',
the keyword ''timeline-scope/all'',
or a list of [=CSS identifiers=]
Animation type: not animatable
Values have the following meanings:
- none
-
No changes in timeline name scope.
- all
-
Specifies that all timeline names defined
by this element or its [=flat tree=] descendants--
whose scope is not already limited by a descendant using 'timeline-scope'--
to be in scope only for this element's [=flat tree=] descendants;
and limits descendants to only match timeline names
to elements within this subtree.
- <>
-
Specifies that a matching timeline name defined
by this element or its [=flat tree=] descendants--
whose scope is not already limited by a descendant using 'timeline-scope'--
to be in scope only for this element's [=flat tree=] descendants;
and limits descendants to only match timeline names
to elements within this subtree.
Note: This property cannot affect or invalidate any timeline name lookups
within the subtree of a descendant element that declares the same name.
See [[#timeline-scope]].
Say you have multiple instances of a component,
each defining a timeline
and some element using that timeline:
```html
```
Both instances of `animation-timeline:--t`
will [=target timeline|target=] the *same* timeline,
i.e. the last one seen in [=flat tree=] order, globally.
If we instead want each instance of `animation-timeline:--t`
to [=target timeline|target=] the "locally defined" `--t`,
we can do the following:
```html
```
Now, any lookup of `--t` taking place within a component
will only see timelines defined inside that component.
# Changes # {#changes}
Changes since the
(28 April 2023)
Working Draft include:
* Changed named timeline lookup to nearest-ancestor/last-in-tree-order.
(Issue 12581),
(Issue 13364)
* Allow ''all'' as a value for 'timeline-scope'.
(Issue 9158)
* Removed scroll-timeline-attachment and view-timeline-attachment in favor of 'timeline-scope'.
(Issue 7759)
* Switched named timelines to use <> instead of <> in order to avoid name clashes with standard CSS keywords.
(Issue 8746)
* Removed {{getCurrentTime()}} API due to various issues in the design.
(Issue 8765)
Progress on an individual animation can now be retrieved using Animation.progress
(Issue 8799)
and other use cases are deferred to Level 2.
* Allow ''scroll-timeline-name/none'' to be used as part of the value list
for 'scroll-timeline-name' and 'view-timeline-name',
not just as an alternative to it.
(Issue 8843)
See also Earlier Changes.