<style>
	/* crbug.com/1471465 */
	dl.switch > dt {
		counter-increment: list-item 0;
	}
</style>
<pre class='metadata'>
Title: Scroll-driven Animations Module Level 1
Group: CSSWG
Status: ED
Work Status: revising
Shortname: scroll-animations
Level: 1
Group: CSSWG
TR: https://www.w3.org/TR/scroll-animations-1/
ED: https://drafts.csswg.org/scroll-animations-1/
Abstract: Defines CSS properties and an API for creating animations that are tied to
          the scroll offset of a scroll container.
Editor: Brian Birtles, Invited Expert, brian@birchill.co.jp, w3cid 43194
Editor: Botond Ballo, Mozilla, botond@mozilla.com, w3cid 94018
Editor: Antoine Quint, Apple, graouts@apple.com, w3cid 51377
Editor: Olga Gerchikov, Microsoft, gerchiko@microsoft.com, w3cid 121763
Editor: Elika J. Etemad / fantasai, Apple, http://fantasai.inkedblade.net/contact, w3cid 35400
Editor: Robert Flack, Google, w3cid 98451
Editor: Bramus Van Damme, Google, https://www.bram.us/about/, w3cid 137581

Former Editor: Majid Valipour, Google, majidvp@google.com, w3cid 81464
Former editor: Mantaroh Yoshinaga
Former editor: Stephen McGruer, Google, smcgruer@google.com
Markup Shorthands: markdown yes
</pre>
<pre class=anchors>
urlPrefix: https://html.spec.whatwg.org/multipage/window-object.html; type: dfn; spec: html
	text: document associated with a window; url: concept-document-window
urlPrefix: https://drafts.csswg.org/web-animations-2/; type: dfn; spec: web-animations-2
	text: calculating an auto-aligned start time
</pre>
<pre class=link-defaults>
spec:web-animations-1;
	type:interface; text:AnimationTimeline
	type:attribute; text:currentTime
	type:dfn;
		text:current time
		text:active interval
		text:start delay
		text:end delay
		text:active duration
		text:iteration count
		text:iteration duration
		text:finished play state
		text:play state
		text:start time
		text:effective playback rate
		text:effect value
spec:html;
	type:dfn; for:/; text:browsing context
	type:method; text:requestAnimationFrame()
spec: cssom-view-1; type: dfn;
	text: overflow direction;
	text: css layout box
spec:css-writing-modes-4; type: dfn;
	text:start
	text:end
spec:infra; type:dfn; text:user agent
spec:selectors-4; type:dfn; text:selector
</pre>

<style>
/* Put nice boxes around each algorithm. */
[data-algorithm]:not(.heading) {
	padding: .5em;
	border: thin solid #ddd; border-radius: .5em;
	margin: .5em calc(-0.5em - 1px);
}
[data-algorithm]:not(.heading) > :first-child {
	margin-top: 0;
}
[data-algorithm]:not(.heading) > :last-child {
	margin-bottom: 0;
}
[data-algorithm] [data-algorithm] {
	margin: 1em 0;
}
</style>

# Introduction # {#intro}

	This specification defines mechanisms for
	driving the progress of an animation
	based on the scroll progress of a scroll container.
	These <dfn export>scroll-driven animations</dfn>
	use a timeline based on scroll position,
	rather than one based on clock time.
	This module provides both an imperative API building on the Web Animations API
	as well as a declarative API building on CSS Animations.
	<!-- [[!CSS-ANIMATIONS-2]] bikeshed chokes on this -->
	[[!WEB-ANIMATIONS-1]]

	There are two types of <dfn export>scroll-driven timelines</dfn>:
	* [[#scroll-timelines|Scroll Progress Timelines]],
		which are linked to the scroll progress of a particular [=scroll container=]
	* [[#view-timelines|View Progress Timelines]],
		which are linked to the view progress of a particular [=box=] through a [=scrollport=]

	Note: Scroll-<em>driven</em> animations,
	whose progress is linked to the scroll position,
	are distinct from scroll-<em>triggered</em> animations,
	which are triggered by a scroll position,
	but whose progress is driven by time.

## Relationship to other specifications ## {#other-specs}

	Web Animations [[WEB-ANIMATIONS-1]] defines
	an abstract conceptual model for animations on the Web platform,
	with elements of the model including [=animations=] and their [=timelines=],
	and associated programming interfaces.
	This specification extends the Web Animations model
	by defining [=scroll-driven timelines=]
	and allowing them to drive progress in [=animations=]
	to create [=scroll-driven animations=].

	This specification introduces both
	programming interfaces for interacting with these concepts,
	as well as CSS properties that apply these concepts
	to CSS Animations [[CSS-ANIMATIONS-1]].
	To the extent the behavior of these CSS properties is described
	in terms of the programming interfaces,
	[=User agents=] that do not support scripting
	may still conform to this specification
	by implementing the CSS features to behave
	as if the underlying programming interfaces were in place.

	Like most operations in CSS besides [=selector=] matching,
	features in this specification operate over
	the [=flattened element tree=].

## Relationship to asynchronous scrolling ## {#async-scrolling}

	Some user agents support scrolling that is asynchronous
	with respect to layout or script.
	This specification is intended to be compatible with such an architecture.

	Specifically, this specification allows expressing scroll-driven effects
	in a way that does not require script to run each time the effect is sampled.
	User agents that support asynchronous scrolling are allowed (but not required)
	to sample such effects asynchronously as well.

## Value Definitions ## {#values}

	This specification follows the
	<a href="https://www.w3.org/TR/CSS2/about.html#property-defs">CSS property definition conventions</a>
	from [[!CSS2]]
	using the <a href="https://www.w3.org/TR/css-values-3/#value-defs">value definition syntax</a>
	from [[!CSS-VALUES-3]].
	Value types not defined in this specification
	are defined in CSS Values &amp; Units [[!CSS-VALUES-3]].
	Combination with other CSS modules may expand the definitions of these value types.

	In addition to the property-specific values listed in their definitions,
	all properties defined in this specification
	also accept the <a>CSS-wide keywords</a> as their property value.
	For readability they have not been repeated explicitly.

# Scroll Progress Timelines # {#scroll-timelines}

	<dfn export>Scroll progress timelines</dfn>
	are timelines linked to progress
	in the scroll position of a [=scroll container=]
	along a [=scrollable axis=].
	The startmost scroll position represents 0% progress
	and the endmost scroll position represents 100% progress.

	[=Scroll progress timelines=] can be referenced in 'animation-timeline'
	anonymously using the ''scroll()'' [=functional notation=]
	or by name (see [[#timeline-scoping]])
	after declaring them using the 'scroll-timeline' properties.
	In the Web Animations API,
	they can be represented anonymously by a {{ScrollTimeline}} object.

## Calculating Progress for a Scroll Progress Timeline ## {#scroll-timeline-progress}

	Progress (the [=timeline/current time=]) for a [=scroll progress timeline=]
	is calculated as:
	<var ignore=''>[=scroll offset=]</var> &div;
	(<var ignore=''>[=scrollable overflow=] size</var> &minus;
		<var ignore=''>[=scroll container=] size</var>)

	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=],
	[=scroll progress timelines=] that would otherwise reference the document viewport
	are also [=inactive timeline|inactive=].

## Anonymous Scroll Progress Timelines ## {#scroll-timelines-anonymous}

### The ''scroll()'' notation ### {#scroll-notation}

	The <dfn>scroll()</dfn> functional notation
	can be used as a <<single-animation-timeline>> value in 'animation-timeline'
	and specifies a [=scroll progress timeline=].
	Its syntax is

	<pre class="prod">
		<<scroll()>> = scroll( [ <<scroller>> || <<axis>> ]? )
		<dfn noexport><<axis>></dfn> = block | inline | x | y
		<dfn noexport><<scroller>></dfn> = root | nearest | self
	</pre>

	By default,
	''scroll()'' references the [=block axis=] of the nearest ancestor [=block-axis scroll container=].
	Its arguments modify this lookup as follows:

	<dl dfn-type=value dfn-for="scroll(),scroll-timeline-axis,view-timeline-axis">
		<dt><dfn>block</dfn>
		<dd>
			Specifies to use the measure of progress along the
			[=block axis=] of the [=block-axis scroll container=].
			(Default.)

		<dt><dfn>inline</dfn>
		<dd>
			Specifies to use the measure of progress along the
			[=inline axis=] of the [=inline-axis scroll container=].

		<dt><dfn>x</dfn>
		<dd>
			Specifies to use the measure of progress along the
			[=horizontal axis=] of the [=x-axis scroll container=].

		<dt><dfn>y</dfn>
		<dd>
			Specifies to use the measure of progress along the
			[=vertical axis=] of the [=y-axis scroll container=].

		<dt><dfn>nearest</dfn>
		<dd>
			Specifies to use the nearest ancestor [=scroll container=]
			with an appropriate [=scrollable axis=]
			(Default.)

		<dt><dfn>root</dfn>
		<dd>
			Specifies to use the document viewport as the [=scroll container=].

		<dt><dfn>self</dfn>
		<dd>
			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=].
	</dl>

	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=]).

	<div class=example>
		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
		<html>
			…
			<body>
				<!-- Note: .scroller is a scrollable box -->
				<div class="scroller">
					…
					<div class="subject">Animation Subject</div>
					…
				</div>
			</body>
		</html>
		```

		The `.subject`‘s animation is set up to be driven by
		an anonymous [=Scroll progress timeline=] created with ''scroll()''.
		Because no <<scroller>> or <<axis>> 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.
	</div>

	<div class=example>
		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);
		}
		```
	</div>

	<div class=example>
		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 <<scroller>> and <<axis>>
		are ''nearest'' and ''block'' respectively.
	</div>

	<div class=example>
		To animate the scroller itself, use the ''self'' keyword as the <<scroller>>

		```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);
		}
		```
	</div>

	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}

	<pre class="idl">
		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;
		};
	</pre>

	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=].

	<dl class="attributes" dfn-type=attribute dfn-for=ScrollTimeline>
		:   <dfn>source</dfn>
		::  The [=scroll container=] element
			whose scroll position drives the progress of the timeline.

		:   <dfn>axis</dfn>
		::  The axis of scrolling
			that drives the progress of the timeline.
			See value definitions for <<axis>>, above.
	</dl>

	Inherited attributes:
	<dl>
		:   {{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=].
	</dl>

	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?
	<!-- turn this into a note instead of a question once resolved -->

	ISSUE: Add a note about whether {{AnimationTimeline/currentTime}}
	can be negative or > 100%.

	<dl class="constructors">
		:   <dfn constructor for=ScrollTimeline lt="ScrollTimeline(options)">ScrollTimeline(options)</dfn>
		::  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:

				<dl class="switch">
					:   If the `source` member of |options| is present,
					::  The `source` member of |options|.

					:   Otherwise,
					::  The {{Document/scrollingElement}}
						of the {{Document}} <a lt="document	associated with a window">associated</a>
						with the {{Window}} that is the <a>current global object</a>.
				</dl>

			1.  Set the {{ScrollTimeline/axis}} property of |timeline|
				to the corresponding value from |options|.
	</dl>

	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.

	<div class=example>
		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.
	</div>

	<div class=example>
		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.
	</div>

## 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 <dfn>named scroll progress timelines</dfn>
	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]].

	<div class=example>
		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.
	</div>

### Naming a Scroll Progress Timeline: the 'scroll-timeline-name' property ### {#scroll-timeline-name}

	<pre class='propdef'>
	Name: scroll-timeline-name
	Value: [ none | <<dashed-ident>> ]#
	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
	</pre>

	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}

	<pre class='propdef'>
	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
	</pre>

	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}

	<pre class='propdef shorthand'>
	Name: scroll-timeline
	Value: [ <<'scroll-timeline-name'>> <<'scroll-timeline-axis'>>? ]#
	Applies to: all elements
	Inherited: no
	Animation type: not animatable
	</pre>

	This property is a [=shorthand=] for setting
	'scroll-timeline-name' and 'scroll-timeline-axis'
	in a single declaration.

	<div class=example>
		The following two rules are equivalent:

		```css
		.scroller {
			scroll-timeline-name: --my-scroller;
			scroll-timeline-axis: inline;
		}
		```

		```css
		.scroller {
			scroll-timeline: --my-scroller inline;
		}
		```
	</div>

# 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 <dfn>view progress subject</dfn>)
	is in view within the [=scrollport=].
	<dfn export>View progress timelines</dfn>
	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=]:

	<dl dfn-for="animation-timeline-range" dfn-type="value">
		<dt><dfn>cover</dfn>
		<dd>
			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=].

		<dt><dfn>contain</dfn>
		<dd>
			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=].

		<dt><dfn>entry</dfn>
		<dd>
			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.

		<dt><dfn>exit</dfn>
		<dd>
			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.

		<dt><dfn>entry-crossing</dfn>
		<dd>
			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=].

		<dt><dfn>exit-crossing</dfn>
		<dd>
			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=].

		<dt><dfn>scroll</dfn>
		<dd>
			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=].
	</dl>

	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=].
	<a href="http://www.w3.org/TR/css-transforms/">Transforms</a> 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| &div; |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 start of the ''animation-timeline-range/cover'' range
		minus
		the [=scroll offset=] corresponding to
		the end 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 <dfn>view()</dfn> functional notation
	can be used as a <<single-animation-timeline>> 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

	<pre class="prod">
		<<view()>> = view( [ <<axis>> || <<'view-timeline-inset'>> ]? )
	</pre>

	By default,
	''view()'' references the [=block axis=];
	as for ''scroll()'',
	this can be changed by providing an explicit <<axis>> value.

	The optional <<'view-timeline-inset'>> value provides an adjustment
	of the [=view progress visibility range=],
	as defined for 'view-timeline-inset'.

	<div class=example>
		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 <<axis>> 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=].
	</div>

	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}

	<pre class="idl">
		dictionary ViewTimelineOptions {
		  Element subject;
		  ScrollAxis axis = "block";
		  (DOMString or sequence&lt;(CSSNumericValue or CSSKeywordValue)>) inset = "auto";
		};

		[Exposed=Window]
		interface ViewTimeline : ScrollTimeline {
		  constructor(optional ViewTimelineOptions options = {});
		  readonly attribute Element subject;
		  readonly attribute CSSNumericValue startOffset;
		  readonly attribute CSSNumericValue endOffset;
		};
	</pre>

	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=].

	<dl class="attributes" dfn-type=attribute dfn-for=ViewTimeline>
		:   <dfn>subject</dfn>
		::  The element whose [=principal box=]’s visibility in the [=scrollport=]
			defines the progress of the timeline.

		:   <dfn>startOffset</dfn>
		::  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=].

		:   <dfn>endOffset</dfn>
		::  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=].
	</dl>

	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:

	<dl>
		:   {{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 <<axis>>, 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=].
	</dl>

	<dl class="constructors">
		:   <dfn constructor for=ViewTimeline lt="ViewTimeline(options)">ViewTimeline(options)</dfn>
		::  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 <span class="exceptionname">TypeError</span>.

				These insets define the {{ViewTimeline}}’s [=view progress visibility range=].
	</dl>

	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.

	<div class=example>
		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`.
	</div>

## 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 <dfn>named view progress timelines</dfn>
	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]].

	<div class=example>
		This example behaves exactly the same
		<a href="#example-6a3831a1">as the previous one</a>:
		each child of the `.scroller` element will reveal itself
		as it crosses the scrollport.

		The difference is that
		instead of using an <a href="#view-timelines-anonymous">anonymous</a> [=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;
		}
		```
	</div>

### Naming a View Progress Timeline: the 'view-timeline-name' property ### {#view-timeline-name}

	<pre class='propdef'>
	Name: view-timeline-name
	Value: [ none | <<dashed-ident>> ]#
	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
	</pre>

	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}

	<pre class='propdef'>
	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
	</pre>

	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}

	<pre class='propdef'>
	Name: view-timeline-inset
	Value: [ [ auto | <<length-percentage>> ]{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 <<length-percentage>> value
	Animation type: by computed value type
	</pre>

	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 <dfn>view progress visibility range</dfn>.

	<dl dfn-for=view-timeline-inset dfn-type=value>
		<dt><dfn>auto</dfn>
		<dd>
			Indicates to use the value of 'scroll-padding'.

		<dt><dfn><<length-percentage>></dfn>
		<dd>
			Like 'scroll-padding',
			defines an inward offset from the corresponding edge of the scrollport.
	</dl>

### View Timeline Shorthand: the 'view-timeline' shorthand ### {#view-timeline-shorthand}

	<pre class='propdef shorthand'>
	Name: view-timeline
	Value: [ <<'view-timeline-name'>> [ <<'view-timeline-axis'>> || <<'view-timeline-inset'>> ]? ]#
	Applies to: all elements
	</pre>

	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 <!-- delays ('animation-delay') and -->
	iterations ('animation-iteration-count')
	are set within the limits of this finite range.
	If the specified duration is ''animation-duration/auto'',
	then <!-- once any delays are subtracted, -->
	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.

	<div algorithm>
		The <dfn>target timeline</dfn>
		for a given element |element|
		referencing a timeline name |name| is:

		<dl>
			:  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|.
		</dl>
	</div>

	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=].
	<div class=example>
		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;
			}
		```
	</div>

	<div class=example>
		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.

		<xmp highlight=html>
		<style>
			@keyframes anim {
			  from { color: red; }
			  to { color: green; }
			}

			.root {
			  /* declares the scope of a '--scroller' timeline to reach all descendants */
			  timeline-scope: --scroller;
			}

			.root .animation {
			  animation: anim;
			  /* references the '--scroller' timeline for driving the progress of 'anim' */
			  animation-timeline: --scroller;
			}

			.root .animation + .scroller {
			  /* attaches a scroll progress timeline to the timeline named '--scroller' */
			  scroll-timeline: --scroller;
			}
		</style>
		&hellip;
		<section class="root">
			<div class="animation">Animating Box</div>
			<div class="scroller">Scrollable Box</div>
		</section>
		</xmp>
	</div>

## 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 <code>animationstart</code> event will fire
	at the <em>end</em> of the [=active interval=],
	and the <code>animationend</code> event will fire
	at the start of the [=active interval=].
	However, since the <code>finish</code> 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 <dfn>layout cycles</dfn>,
	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 <dfn export>stale timelines</dfn> 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 <a href="https://www.w3.org/TR/css-animations/">CSS Animations</a>
	and <a href="https://www.w3.org/TR/web-animations/">Web Animations</a>.

## Named Timeline Ranges ## {#named-ranges}

	A <dfn export>named timeline range</dfn>
	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 <dfn><<timeline-range-name>></dfn> 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:

	<pre class="prod">
		<<keyframe-selector>> = from | to | <<percentage [0,100]>> | <<timeline-range-name>> <<percentage>>
	</pre>

	where <<timeline-range-name>> is the [=CSS identifier=]
	that represents a chosen predefined [=named timeline range=],
	and the <<percentage>> 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).

	<div class=example>
		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;
		}
		```
	</div>

## Attaching Animations to Timeline Ranges ## {#named-range-animation-declaration}

	A set of animation keyframes can be attached
	in reference to an <dfn export local-lt="attachment range">animation attachment range</dfn>,
	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=].

	<pre class="ascii-art">
	range start┐             ╺┉┉active interval┉┉╸           ┌range end
	┄┄┄┄┄┄┄┄┄┄┄├─────────────╊━━━━━━━━━━━━━━━━━━━╉───────────┤┄┄┄┄┄┄┄┄
	           ╶┄start delay┄╴                   ╶┄end delay┄╴
	                         ╶┄┄┄┄┄ duration┄┄┄┄┄╴
	</pre>

	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}

	<div class=example>
		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`.
	</div>

	<div class=example>
		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.
	</div>

	<div class=example>
		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.
		
	</div>

### Specifying an Animation’s Timeline Range: the 'animation-range' shorthand ### {#animation-range}

	<pre class="propdef shorthand">
		Name: animation-range
		Value: [ <<'animation-range-start'>> <<'animation-range-end'>>? ]#
	</pre>

	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 <<timeline-range-name>> component,
	then 'animation-range-end' is set to that same <<timeline-range-name>> and ''100%''.
	Otherwise, any omitted [=longhand=] is set to its [=initial value=].

	<div class="example">
		The following sets of declarations show an 'animation-range' [=shorthand=] declaration
		followed by its equivalent 'animation-range-start' and 'animation-range-end' declarations:
		<pre style="columns: 40ch">
			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%;
		</pre>
	</div>

	<div class=example>
		Because <<'animation-range-start'>> and <<'animation-range-end'>> accept <<length-percentage>>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);
		}
		```
	</div>

	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}

	<pre class="propdef">
		Name: animation-range-start
		Value: [ normal | <<length-percentage>> | <<timeline-range-name>> <<length-percentage>>? ]#
		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
	</pre>

	Specifies the start of the animations’s [=attachment range=],
	shifting the <a spec="web-animations-1">start time</a> 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:

	<dl dfn-for="animation-range-start" dfn-type=value>
		<dt><dfn>normal</dfn>
		<dd>
			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.

		<dt><dfn><<length-percentage>></dfn>
		<dd>
			The [=animation attachment range=] starts
			at the specified point on the [=timeline=]
			measuring from the start of the timeline.

		<dt><dfn><<timeline-range-name>> <<length-percentage>>?</dfn>
		<dd>
			The [=animation attachment range=] starts
			at the specified point on the [=timeline=]
			measuring from the start of the specified [=named timeline range=].
			If the <<length-percentage>> is omitted,
			it defaults to 0%.
	</dl>

### Specifying an Animation’s Timeline Range End: the 'animation-range-end' property ### {#animation-range-end}

	<pre class="propdef">
		Name: animation-range-end
		Value: [ normal | <<length-percentage>> | <<timeline-range-name>> <<length-percentage>>? ]#
		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
	</pre>

	Specifies the end of the animations’s [=attachment range=],
	potentially shifting the <a spec="web-animations-1">end time</a> 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:

	<dl dfn-for="animation-range-end" dfn-type=value>
		<dt><dfn>normal</dfn>
		<dd>
			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.

		<dt><dfn><<length-percentage>></dfn>
		<dd>
			The [=animation attachment range=] ends
			at the specified point on the [=timeline=]
			measuring from the start of the timeline.

		<dt><dfn><<timeline-range-name>> <<length-percentage>>?</dfn>
		<dd>
			The [=animation attachment range=] ends
			at the specified point on the [=timeline=]
			measuring from the start of the specified [=named timeline range=].
			If the <<length-percentage>> is omitted,
			it defaults to 100%.
	</dl>

# 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}
	<pre class="propdef">
		Name: timeline-scope
		Value: none | all | <<dashed-ident>>#
		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
	</pre>

	Values have the following meanings:

	<dl dfn-for="timeline-scope" dfn-type=value>
		<dt><dfn>none</dfn>
		<dd>
			No changes in timeline name scope.

		<dt><dfn>all</dfn>
		<dd>
			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.

		<dt><dfn><<dashed-ident>></dfn>
		<dd>
			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.
	</dl>

	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]].

	<div class=example>
		Say you have multiple instances of a component,
		each defining a timeline
		and some element using that timeline:

		```html
		<div>
		  <div class="component">
		    <div style="view-timeline: --t /*...*/;"></div>
		    <div style="animation-timeline: --t;"></div>
		  </div>
		  <div class="component">
		    <div style="view-timeline: --t /*...*/;"></div>
		    <div style="animation-timeline: --t;"></div>
		  </div>
		</div>
		```

		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
		<style>
		  .component {
		    timeline-scope: --t;
		  }
		</style>
		```

		Now, any lookup of `--t` taking place within a component
		will only see timelines defined inside that component.

	</div>

# Changes # {#changes}

	Changes since the
	(<a href="https://www.w3.org/TR/2023/WD-scroll-animations-1-20230428/">28 April 2023</a>)
	Working Draft include:

	* Changed named timeline lookup to nearest-ancestor/last-in-tree-order.
		(<a href="https://github.com/w3c/csswg-drafts/issues/12581">Issue 12581</a>),
		(<a href="https://github.com/w3c/csswg-drafts/issues/13364">Issue 13364</a>)
	* Allow ''all'' as a value for 'timeline-scope'.
		(<a href="https://github.com/w3c/csswg-drafts/issues/9158">Issue 9158</a>)
	* Removed <css>scroll-timeline-attachment</css> and <css>view-timeline-attachment</css> in favor of 'timeline-scope'.
		(<a href="https://github.com/w3c/csswg-drafts/issues/7759">Issue 7759</a>)
	* Switched named timelines to use <<dashed-ident>> instead of <<custom-ident>> in order to avoid name clashes with standard CSS keywords.
		(<a href="https://github.com/w3c/csswg-drafts/issues/8746">Issue 8746</a>)
	* Removed {{getCurrentTime()}} API due to various issues in the design.
		(<a href="https://github.com/w3c/csswg-drafts/issues/8765">Issue 8765</a>)
		Progress on an individual animation can now be retrieved using <code>Animation.progress</code>
		(<a href="https://github.com/w3c/csswg-drafts/issues/8799">Issue 8799</a>)
		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.
		(<a href="https://github.com/w3c/csswg-drafts/issues/8843">Issue 8843</a>)

	See also <a href="https://www.w3.org/TR/2023/WD-scroll-animations-1-20230428/#changes">Earlier Changes</a>.
