1. Introduction
This section is non-normative.
View Transitions is a feature that allows developers to create animated transitions between visual states of the document.
1.1. Separating transitions from DOM updates
Traditionally, creating a transition between two states involves a period where both states are present in the DOM at the same time. In fact, it usually involves creating a specific DOM structure that can contain both states. If one element is "moving" between containers, that element often needs to exist outside of either container for the period of the transition, to avoid clipping from either container or ancestor elements.
This extra in-between state often results in UX and accessibility issues, as the structure of the DOM is compromised for a purely-visual effect.
View transitions avoid this troublesome in-between state by allowing the DOM to switch between states synchronously, then performing a customizable visual transition between the two states in another layer, using a static capture of the old state, and a live capture of the new state.
These captures are used in a tree of pseudo-elements (detailed in § 5.3 View transition pseudo-elements), where the old state cross-fades with the new state, while animating from the old to new size and position.
Since the captures are rendered in pseudo-elements, developers can customize the transition using familiar features, such as CSS and web animations.
The developer chooses which elements are captured independently, meaning they can be animated independently, using the view-transition-name CSS property.
1.2. Lifecycle
A successful view transition goes through the following phases:
-
Developer calls
document.
, which returnsstartViewTransition
(updateCallback
)viewTransition
, aViewTransition
. -
Current state captured as the “old” state.
-
Rendering paused.
-
Developer’s
updateCallback
is called, where they update document state. -
viewTransition.
fulfills.updateCallbackDone
-
Current state captured as the “new” state.
-
Transition pseudo-elements created. See § 5.3 View transition pseudo-elements for an overview of this structure.
-
Rendering unpaused, revealing the transition pseudo-elements.
-
viewTransition.
fulfills.ready
-
Pseudo-elements animate until finished.
-
Transition pseudo-elements removed.
-
viewTransition.
fulfills.finished
1.3. Transitions as an enhancement
A key part of this API design is that an animated transition is an enhancement to a document state change.
That means, a failure to create a transition,
which can happen due to misconfiguration or device constraints,
will not prevent the developer’s UpdateCallback
being called,
even if it’s known in advance that the animated transition cannot happen.
For example, if the developer calls skipTransition()
at the start of the lifecycle,
the steps relating to the animated transition, such as creating the pseudo-elements, will not happen.
However, the UpdateCallback
will still be called.
It’s only the transition that’s skipped, not the whole state change.
If the DOM change should also be skipped, then that should be handled by another feature.
is an example of a feature developers could use to handle this.navigateEvent
.signal
Although the transition API allows DOM changes to be asynchronous via the UpdateCallback
,
the transition API is not responsible for queuing or otherwise scheduling the DOM changes beyond the scheduling needed for the transition itself.
Some asynchronous DOM changes can happen concurrently (e.g if they’re happening within independent components),
whereas others need to queue, or abort an earlier change.
This is best left to a feature or framework that has a more holistic view of the application.
1.4. Examples
function spaNavigate( data) { updateTheDOMSomehow( data); }
A transition could be added like this:
function spaNavigate( data) { // Fallback for browsers that don’t support this API: if ( ! document. startViewTransition) { updateTheDOMSomehow( data); return ; } // With a transition: document. startViewTransition(() => updateTheDOMSomehow( data)); }
This results in the default transition of a quick cross-fade:
The cross-fade is achieved using CSS animations on a tree of pseudo-elements, so customizations can be made using CSS. For example:
::view-transition-old ( root), ::view-transition-new ( root) { animation-duration : 5 s ; }
This results in a slower transition:
@keyframes fade-in{ from{ opacity : 0 ; } } @keyframes fade-out{ to{ opacity : 0 ; } } @keyframes slide-from-right{ from{ transform : translateX ( 30 px ); } } @keyframes slide-to-left{ to{ transform : translateX ( -30 px ); } } ::view-transition-old ( root) { animation : 90 ms cubic-bezier ( 0.4 , 0 , 1 , 1 ) both fade-out, 300 ms cubic-bezier ( 0.4 , 0 , 0.2 , 1 ) both slide-to-left; } ::view-transition-new ( root) { animation : 210 ms cubic-bezier ( 0 , 0 , 0.2 , 1 ) 90 ms both fade-in, 300 ms cubic-bezier ( 0.4 , 0 , 0.2 , 1 ) both slide-from-right; }
Here’s the result:
.main-header{ view-transition-name : main-header; } .main-header-text{ view-transition-name : main-header-text; /* Give the element a consistent size, assuming identical text: */ width: fit-content; }
By default, these groups will transition size and position from their “old” to “new” state, while their visual states cross-fade:
In this case, things would look better if the sidebar was static if it was in both the “old” and “new” states. Otherwise, it should animate in or out.
The :only-child pseudo-class can be used to create animations specifically for these states:
.sidebar{ view-transition-name : sidebar; } @keyframes slide-to-right{ to{ transform : translateX ( 30 px ); } } /* Entry transition */ ::view-transition-new ( sidebar) :only-child{ animation : 300 ms cubic-bezier ( 0 , 0 , 0.2 , 1 ) both fade-in, 300 ms cubic-bezier ( 0.4 , 0 , 0.2 , 1 ) both slide-from-right; } /* Exit transition */ ::view-transition-old ( sidebar) :only-child{ animation : 150 ms cubic-bezier ( 0.4 , 0 , 1 , 1 ) both fade-out, 300 ms cubic-bezier ( 0.4 , 0 , 0.2 , 1 ) both slide-to-right; }
For cases where the sidebar has both an “old” and “new” state, the default animation is correct.
Firstly, in the CSS, allow the “old” and “new” states to layer on top of one another without the default blending, and prevent the default cross-fade animation:
::view-transition-image-pair ( root) { isolation : auto; } ::view-transition-old ( root), ::view-transition-new ( root) { animation : none; mix-blend-mode : normal; }
Then, the JavaScript:
// Store the last click event let lastClick; addEventListener( 'click' , event=> ( lastClick= event)); function spaNavigate( data) { // Fallback for browsers that don’t support this API: if ( ! document. startViewTransition) { updateTheDOMSomehow( data); return ; } // Get the click position, or fallback to the middle of the screen const x= lastClick? . clientX?? innerWidth/ 2 ; const y= lastClick? . clientY?? innerHeight/ 2 ; // Get the distance to the furthest corner const endRadius= Math. hypot( Math. max( x, innerWidth- x), Math. max( y, innerHeight- y) ); // Create a transition: const transition= document. startViewTransition(() => { updateTheDOMSomehow( data); }); // Wait for the pseudo-elements to be created: transition. ready. then(() => { // Animate the root’s new view document. documentElement. animate( { clipPath: [ `circle(0 at ${ x} px ${ y} px)` , `circle( ${ endRadius} px at ${ x} px ${ y} px)` , ], }, { duration: 500 , easing: 'ease-in' , // Specify which pseudo-element to animate pseudoElement: '::view-transition-new(root)' , } ); }); }
And here’s the result:
2. CSS properties
2.1. view-transition-name
Name: | view-transition-name |
---|---|
Value: | none | <custom-ident> |
Initial: | none |
Applies to: | all elements |
Inherited: | no |
Percentages: | n/a |
Computed value: | as specified |
Canonical order: | per grammar |
Animation type: | discrete |
The view-transition-name property "names" an element as participating in a view transition.
- none
-
The element will not participate in a view transition.
- <custom-ident>
-
The element can participate in a view transition, as either an old or new element, with a view transition name equal to the <custom-ident>'s value.
Note: The value none is invalid as a <custom-ident>.
Note: This property causes the user-agent to both capture separate snapshots from the elements, as well as create separate pseudo-element sub-trees representing this element’s “old” and “new” states. For the purposes of this API, if one element has a transition-name "foo" in the old state, and another element has a transition-name "foo" in the new state, they are treated as representing different visual state of the same element. This may be confusing, since the elements themselves are not necessarily referring to the same object, but it is a useful model to consider them to be visual states of the same conceptual page entity, that we happen to call "element".
3. Layout and rendering changes
3.1. Named and transitioning elements
Elements have an involved in a view transition boolean, initially false.
Elements that either have a view-transition-name computed value that is not none, or are involved in a view transition, form:
-
a group.
Note: This spec uses CSS’s definition of element, which includes pseudo-elements.
3.2. Interactions during suppressed rendering
While a Document
document’s transition suppressing rendering is true,
pointer hit testing must target document’s document element,
ignoring all other elements.
Note: This does not effect pointers that are captured.
4. User-agent styles
The global view transition user agent style sheet is a style sheet in the user-agent origin, used in all namespaces.
CSS needs a central place for an "all namespaces" style sheet.
It contains the following:
@keyframes -ua-view-transition-fade-out{ to{ opacity : 0 ; } } @keyframes -ua-view-transition-fade-in{ from{ opacity : 0 ; } } :root{ view-transition-name : root; }
5. Pseudo-elements
5.1. Pseudo-element root
Note: This is a general definition for trees of pseudo-elements. If other features need this behavior, these definitions will be moved to [css-pseudo-4].
A pseudo-element root is a type of tree-abiding pseudo-element that is the root in a tree of tree-abiding pseudo-elements, known as the pseudo-element tree.
The pseudo-element tree defines the document order of its descendant tree-abiding pseudo-elements.
When a pseudo-element participates in a pseudo-element tree, its originating pseudo-element is its parent.
If a descendant pseudo of a pseudo-element root has no other siblings, then :only-child matches that pseudo.
Note: This means that ::view-transition-new(ident):only-child
will only select ::view-transition-new(ident)
if the parent ::view-transitions-image-pair(ident)
contains a single child.
As in, there is no sibling ::view-transition-old(ident)
.
5.2. Named view-transition pseudo-elements
A named view-transition pseudo-element is a type of tree-abiding pseudo-elements.
It has a view-transition name, a string.
Their selector takes a <pt-name-selector> argument.
<pt-name-selector> = '*' | <custom-ident>
The selector matches if the <pt-name-selector> is *
or matches the named view-transition pseudo-element's view-transition name.
The specificity of a view-transition selector with a <custom-ident> argument is the same as for other pseudo-elements, and is equivalent to a type selector.
The specificity of a view-transition selector with a *
argument is zero.
Note: The view-transition name is set to the view-transition-name that triggered its creation.
5.3. View transition pseudo-elements
::view-transition ├─ ::view-transition-group(name) │ └─ ::view-transition-image-pair(name) │ ├─ ::view-transition-old(name) │ └─ ::view-transition-new(name) └─ …other groups…
Each element with a view-transition-name is captured separately, and a ::view-transition-group() is created for each unique view-transition-name.
For convenience, the document element is given the view-transition-name "root" in the user-agent style sheet.
Either ::view-transition-old() or ::view-transition-new() are absent in cases where the capture does not have an “old” or “new” state.
Each of the pseudo-elements generated can be targeted by CSS in order to customize its appearance, behavior and/or add animations. This enables full customization of the transition.
5.3.1. ::view-transition
A tree-abiding pseudo-element that is also a pseudo-element root. Its originating element is the document’s document element.
Its containing block is the snapshot containing block.
The following is added to the global view transition user agent style sheet:
:root::view-transition{ position : fixed; inset : 0 ; }
5.3.2. ::view-transition-group( <pt-name-selector> )
If there’s both an “old” and “new” state, styles in the view transition style sheet animate this pseudo-element’s width and height from the size of the old element’s border box to that of the new element’s border box.
Also the element’s transform is animated from the old element’s screen space transform to the new element’s screen space transform.
This style is generated dynamically since the values of animated properties are determined at the time that the transition begins.
A tree-abiding pseudo-element that is also a named view-transition pseudo-element, and participates in a pseudo-element tree.
It is selected from its ultimate originating element, the document element.
The following is added to the global view transition user agent style sheet:
:root::view-transition-group ( *) { position : absolute; top : 0 ; left : 0 ; animation-duration : 0.25 s ; animation-fill-mode : both; }
5.3.3. ::view-transition-image-pair( <pt-name-selector> )
It is always present as a child of each ::view-transition-group().
A tree-abiding pseudo-element that is also a named view-transition pseudo-element, and participates in a pseudo-element tree.
It is selected from its ultimate originating element, the document element.
The following is added to the global view transition user agent style sheet:
:root::view-transition-image-pair ( *) { position : absolute; inset : 0 ; animation-duration : inherit; animation-fill-mode : inherit; }
5.3.4. ::view-transition-old( <pt-name-selector> )
This element is a replaced element that produced the visual representation of the “old” state taken from user-agent provided snapshots.
This is only ever a child of a ::view-transition-image-pair(), never has any children, and is omitted if there’s no “old” state to represent.
:only-child can be used to match cases where this element is the only element in the ::view-transition-image-pair().
The appearance of this element can be manipulated with object-*
properties in the same way that other replaced elements can be.
A tree-abiding pseudo-element that is also a named view-transition pseudo-element, and participates in a pseudo-element tree.
It is selected from its ultimate originating element, the document element.
It is a replaced element, with natural dimensions equal to the content’s size.
Note: The image content is captured in capture the image, then set and updated in setup transition pseudo-elements and update pseudo-element styles.
The following is added to the global view transition user agent style sheet:
:root::view-transition-old ( *) { position : absolute; inset-block-start : 0 ; inline-size : 100 % ; block-size : auto; animation-name : -ua-view-transition-fade-out; animation-duration : inherit; animation-fill-mode : inherit; }
Note: The aim of the style is to match the element’s inline size while retaining the aspect ratio. It is also placed at the block start.
Note: Additional styles in the view transition style sheet added to animate these pseudo-elements are detailed in setup transition pseudo-elements and update pseudo-element styles.
5.3.5. ::view-transition-new( <pt-name-selector> )
Identical to ::view-transition-old(), except the following styles added to the global view transition user agent style sheet:
:root::view-transition-new ( *) { position : absolute; inset-block-start : 0 ; inline-size : 100 % ; block-size : auto; animation-name : -ua-view-transition-fade-in; animation-duration : inherit; animation-fill-mode : inherit; }
Note: The construction of this tree is performed in the setup transition pseudo-elements algorithm.
6. Concepts
6.1. Phases
Phases represent an ordered sequence of states. Since phases are ordered, prose can refer to phases before a particular phase, meaning they appear earlier in the sequence.
The initial phase is the first item in the sequence.
6.2. The snapshot containing block
The snapshot containing block is a rectangle that covers all areas of the window that could potentially display web content. This area is consistent regardless of root scrollbars or interactive widgets.
This means the snapshot canvas size is likely to be consistent for the document element's old image and new element.
The snapshot containing block origin refers to the top-left corner of the snapshot containing block.
The snapshot containing block size refers to the width and height of the snapshot containing block as a tuple of two numbers.
The snapshot containing block is considered to be an absolute positioning containing block and a fixed positioning containing block for ::view-transition and its descendants.
6.3. The view-transition layer stacking layer
This specification introduces a stacking layer to the Elaborate Description of Stacking Contexts.
The ::view-transition pseudo-element generates a new stacking context called the view-transition layer with the following characteristics:
-
Its parent stacking context is the root stacking context.
-
If the ::view-transition pseudo-element exists, a new stacking context is created for the document element and the [/=document=]'s top layer. The view-transition layer is a sibling of this stacking context.
-
The view-transition layer paints after the stacking context for the document element and [/=document=]'s top layer. This includes the filters and effects that are applied to the document element.
Note: The intent of the feature is to be able to capture the contents of the page, which includes the top layer elements. In order to accomplish that, the view-transition layer cannot be a part of the captured top layer context, since that results in a circular dependency. Instead, this stacking context is a sibling of other page contents.
6.4. Captured elements
A captured element is a struct with the following:
- old image
-
an
ImageData
or null. Initially null. - old width
- old height
-
an
unrestricted double
, initially zero. - old transform
-
a <transform-function>, initially the identity transform function.
- old writing-mode
-
Null or a writing-mode, initially null.
- old direction
-
Null or a direction, initially null.
- new element
-
an element or null. Initially null.
In addition, a captured element has the following style definitions:
- group keyframes
-
A
CSSKeyframesRule
or null. Initially null. - group animation name rule
-
A
CSSStyleRule
or null. Initially null. - group styles rule
-
A
CSSStyleRule
or null. Initially null. - image pair isolation rule
-
A
CSSStyleRule
or null. Initially null. - view blend mode rule
-
A
CSSStyleRule
or null. Initially null.
Note: These are used to update, and later remove styles from a document's view transition style sheet.
6.5. Additions to Document
A Document
additionally has:
- active view transition
-
a
ViewTransition
or null. Initially null. - transition suppressing rendering
-
a boolean. Initially false.
- view transition style sheet
-
a style sheet. Initially a new style sheet in the user-agent origin, ordered after the global view transition user agent style sheet.
Note: This is used to hold dynamic styles relating to transitions.
- show view-transition root pseudo-element
-
A boolean. Initially false.
When this is true, this's active view transition's transition root pseudo-element renders as a child of this's document element, and this's document element is its originating element.
Note: The position of the transition root pseudo-element within the document element does not matter, as the transition root pseudo-element's containing block is the snapshot containing block.
7. API
7.1. Additions to Document
partial interface Document {ViewTransition startViewTransition (optional UpdateCallback ?=
updateCallback null ); };callback =
UpdateCallback Promise <any > ();
viewTransition
=document
.startViewTransition
(updateCallback
)-
Starts a new view transition.
updateCallback
is called asynchronously, once the current state of the document is captured. Then, when the promise returned byupdateCallback
fulfills, the new state of the document is captured.updateCallback
is always called, even if the transition cannot happen (e.g. due to duplicateview-transition-name
values). The transition is an enhancement around the state change, so a failure to create a transition never prevents the state change. See § 1.3 Transitions as an enhancement for more details on this principle.If the promise returned by
updateCallback
rejects, the transition is skipped.
7.1.1. startViewTransition()
startViewTransition(updateCallback)
are as follows:
-
Let transition be a new
ViewTransition
object in this’s relevant Realm. -
Set transition’s update callback to updateCallback.
-
Let document be this’s relevant global object’s associated document.
-
If document’s active view transition is not null, then skip the view transition document’s active view transition with an "
AbortError
"DOMException
in this’s relevant Realm.Note: This can result in two asynchronous update callbacks running concurrently. One for the document’s current active view transition, and another for this transition. As per the design of this feature, it’s assumed that the developer is using another feature or framework to correctly schedule these DOM changes.
-
Set document’s active view transition to transition.
Note: The process continues in setup view transition, via perform pending transition operations, which is called in § 8.1 Monkey patches to rendering.
-
Return transition.
7.2. The ViewTransition
interface
[Exposed =Window ]interface {
ViewTransition readonly attribute Promise <undefined >;
updateCallbackDone readonly attribute Promise <undefined >;
ready readonly attribute Promise <undefined >;
finished undefined skipTransition (); };
Note: The ViewTransition
represents and controls a single same-document transition.
That is, it controls a transition where the starting and ending document are the same,
possibly with changes to the document’s DOM structure.
viewTransition
.updateCallbackDone
-
A promise that fulfills when the promise returned by
updateCallback
fulfills, or rejects when it rejects.The View Transition API wraps a DOM change and creates a transition. However, sometimes you don’t care about the success/failure of the transition animation, you just want to know if and when the DOM change happens.
updateCallbackDone
is for that use-case. viewTransition
.ready
-
A promise that fulfills once the pseudo-elements for the transition are created, and the animation is about to start.
It rejects if the transition cannot begin. This can be due to misconfiguration, such as duplicate 'view-transition-name’s, or if
updateCallbackDone
returns a rejected promise.The point that
ready
fulfills is the ideal opportunity to animate the view transition pseudo-elements with the web animation API. viewTransition
.finished
-
A promise that fulfills once the end state is fully visible and interactive to the user.
It only rejects if
updateCallback
returns a rejected promise, as this indicates the end state wasn’t created.Otherwise, if a transition fails to begin, or is skipped (from skipTransition()), the end state is still reached, so
finished
fulfills. viewTransition
.skipTransition
()-
Immediately finish the transition, or prevent it starting.
This never prevents
updateCallback
being called, as the DOM change is separate to the transition. See § 1.3 Transitions as an enhancement for more details on this principle.If this is called before
ready
resolves,ready
will reject.If
finished
hasn’t resolved, it will fulfill or reject along withupdateCallbackDone
.
A ViewTransition
has the following:
- named elements
-
a map, whose keys are view transition names and whose values are captured elements. Initially a new map.
- phase
-
One of the following phases:
-
"
pending-capture
". -
"
update-callback-called
". -
"
animating
". -
"
done
".
Note: For the most part, a developer using this API does not need to worry about the different phases, since they progress automatically. It is, however, important to understand what steps happen in each of the phases: when the snapshots are captured, when pseudo-element DOM is created, etc. The description of the phases below tries to be as precise as possible, with an intent to provide an unambiguous set of steps for implementors to follow in order to produce a spec-compliant implementation.
-
- update callback
-
an
UpdateCallback
or null. Initially null. - ready promise
-
a
Promise
. Initially a new promise in this’s relevant Realm. - update callback done promise
-
a
Promise
. Initially a new promise in this’s relevant Realm.Note: The ready promise and update callback done promise are immediately created, so rejections will cause
unhandledrejection
s unless they’re handled, even if the getters such asupdateCallbackDone
are not accessed. - finished promise
-
a
Promise
. Initially a new promise in this’s relevant Realm, marked as handled.Note: This is marked as handled to prevent duplicate
unhandledrejection
s, as this promise only ever rejects along with the update callback done promise. - transition root pseudo-element
-
a ::view-transition. Initially a new ::view-transition.
- initial snapshot containing block size
-
a tuple of two numbers (width and height), or null. Initially null.
Note: This is used to detect changes in the snapshot containing block size, which causes the transition to skip. Discussion of this behavior.
The finished
getter steps are to return this’s finished promise.
The ready
getter steps are to return this’s ready promise.
The updateCallbackDone
getter steps are to return this’s update callback done promise.
7.2.1. skipTransition()
skipTransition()
are:
-
If this's phase is not "
done
", then skip the view transition for this with an "AbortError
"DOMException
.
8. Algorithms
8.1. Monkey patches to rendering
-
For each fully active
Document
in docs, perform pending transition operations for thatDocument
.
Note: These steps will be added to the update the rendering in the HTML spec. As such, the prose style is written to match other steps in that algorithm.
A navigable has no rendering opportunities if active document has transition suppressing rendering set to true.
Note: These steps will be added to the update the rendering in the HTML spec. See #7884 for more context.
8.2. Perform pending transition operations
Document
document, perform the following steps:
-
If document’s active view transition is not null, then:
-
If document’s active view transition's phase is "
pending-capture
", then setup view transition with document’s active view transition. -
Otherwise, if document’s active view transition's phase is "
animating
", then handle transition frame for document’s active view transition.
-
8.3. Setup view transition
ViewTransition
transition,
perform the following steps:
Note: This algorithm captures the current state of the document,
calls the transition’s UpdateCallback
,
then captures the new state of the document.
-
Let document be transition’s relevant global object’s associated document.
-
Capture the old state for transition.
If failure is returned, then skip the view transition for transition with an "
InvalidStateError
"DOMException
in transition’s relevant Realm, and return. -
Set document’s transition suppressing rendering to true.
-
Queue a global task on the DOM manipulation task source, given transition’s relevant global object, to execute the following steps:
Note: A task is queued here because the texture read back in capturing the image may be async, although the render steps in the HTML spec act as if it’s synchronous.
-
If transition’s phase is "
done
", then abort these steps.Note: This happens if transition was skipped before this point. The skip the view transition steps call the update callback, ensuring the transition’s update callback is always called.
-
Call the update callback of transition.
-
React to transition’s update callback done promise:
-
If the promise does not settle within an implementation-defined timeout, then:
-
If transition’s phase is "
done
", then return.Note: This happens if transition was skipped before this point.
-
Skip the view transition transition with a "
TimeoutError
"DOMException
.
-
-
If the promise was rejected with reason reason, then:
-
If transition’s phase is "
done
", then return.Note: This happens if transition was skipped before this point.
-
Mark as handled transition’s ready promise.
Note: transition’s update callback done promise will provide the
unhandledrejection
. This step avoids a duplicate. -
Skip the view transition transition with reason.
-
-
If the promise was fulfilled, then:
-
If transition’s phase is "
done
", then return.Note: This happens if transition was skipped before this point.
-
If transition’s initial snapshot containing block size is not equal to the snapshot containing block size, then skip the view transition for transition, and return.
-
Set transition suppressing rendering to false.
-
Capture the new state for transition.
If failure is returned, then skip the view transition for transition with an "
InvalidStateError
"DOMException
in transition’s relevant Realm, and return. -
For each capturedElement of transition’s named elements' values:
-
If capturedElement’s new element is not null, then set capturedElement’s new element's involved in a view transition to true.
-
-
Setup transition pseudo-elements for transition.
-
Update pseudo-element styles for transition.
If failure is returned, then skip the view transition for transition with an "
InvalidStateError
"DOMException
in transition’s relevant Realm, and return.Note: The above steps will require running document lifecycle phases, to compute information calculated during style/layout.
-
Set transition’s phase to "
animating
". -
Resolve transition’s ready promise.
-
-
-
8.3.1. Capture the old state
ViewTransition
transition:
-
Let document be transition’s relevant global object’s associated document.
-
Let namedElements be transition’s named elements.
-
Let usedTransitionNames be a new set of strings.
-
Let document be transition’s relevant global object’s associated document.
-
Set transition’s initial snapshot containing block size to the snapshot containing block size.
-
For each element of every element that is connected, and has a node document equal to to document, in paint order:
The link for "paint order" doesn’t seem right. Is there a more canonical definition?
-
If any flat tree ancestor of this element skips its contents, then continue.
-
If element has more than one box fragment, then continue.
Note: We might want to enable transitions for fragmented elements in future versions. See #8900.
-
Let transitionName be the computed value of view-transition-name for element.
-
If transitionName is none, or element is not rendered, then continue.
-
If usedTransitionNames contains transitionName, then return failure.
-
Append transitionName to usedTransitionNames.
-
Let capture be a new captured element struct.
-
Set capture’s old image to the result of capturing the image of element.
-
Let originalRect be snapshot containing block if element is the document element, otherwise, the element|'s border box.
-
Set capture’s old height to originalRect’s
height
. -
Set capture’s old transform to a <transform-function> that would map element’s border box from the snapshot containing block origin to its current visual position.
-
Set capture’s [=captured element/old writing-mode] to the computed value of writing-mode on element.
-
Set capture’s [=captured element/old direction] to the computed value of direction on element.
-
Set namedElements[transitionName] to capture.
-
8.3.2. Capture the new state
ViewTransition
transition:
-
Let document be transition’s relevant global object’s associated document.
-
Let namedElements be transition’s named elements.
-
Let usedTransitionNames be a new set of strings.
-
For each element of every element that is connected, and has a node document equal to to document, in paint order:
The link for "paint order" doesn’t seem right. Is there a more canonical definition?
-
If any flat tree ancestor of this element skips its contents, then continue.
-
Let transitionName be the computed value of view-transition-name for element.
-
If transitionName is none, or element is not rendered, then continue.
-
If usedTransitionNames contains transitionName, then return failure.
-
Append transitionName to usedTransitionNames.
-
If namedElements[transitionName] does not exist, then set namedElements[transitionName] to a new captured element struct.
-
Set namedElements[transitionName]'s new element to element.
-
8.3.3. Setup transition pseudo-elements
ViewTransition
transition:
Note: This algorithm constructs the pseudo-element tree for the transition, and generates initial styles. The structure of the pseudo-tree is covered at a higher level in § 5.3 View transition pseudo-elements.
-
Let document be this’s relevant global object’s associated document.
-
Set document’s show view-transition root pseudo-element to true.
-
For each transitionName → capturedElement of transition’s named elements:
-
Let group be a new ::view-transition-group(), with its view-transition name set to transitionName.
-
Append group to transition’s transition root pseudo-element.
-
Let imagePair be a new ::view-transition-image-pair(), with its view-transition name set to transitionName.
-
Append imagePair to group.
-
If capturedElement’s old image is not null, then:
-
Let old be a new ::view-transition-old(), with its view-transition name set to transitionName, displaying capturedElement’s old image.
-
Append old to imagePair.
-
-
If capturedElement’s new element is not null, then:
-
Let new be a new ::view-transition-new(), with its view-transition name set to transitionName.
Note: The styling of this pseudo is handled in update pseudo-element styles.
-
Append new to imagePair.
The new element and its contents (the flat tree descendants of the element, including both text and elements, or the replaced content of a replaced element), except the transition’s transition root pseudo-element's inclusive descendants, are not painted (as if they had visibility: hidden) and do not respond to hit-testing (as if they had pointer-events: none) until new exists.
-
-
If both of capturedElement’s old image and new element are not null, then:
-
Let transform be capturedElement’s old transform.
-
Let width be capturedElement’s old width.
-
Let height be capturedElement’s old height.
-
Set capturedElement’s group keyframes to a new
CSSKeyframesRule
representing the following CSS, and append it to document’s view transition style sheet:@keyframes -ua-view-transition-group-anim-transitionName{ from{ transform : transform; width : width; height : height; } } Note: The above code example contains variables to be replaced.
-
Set capturedElement’s group animation name rule to a new
CSSStyleRule
representing the following CSS, and append it to document’s view transition style sheet::root:
:view-transition-group ( transitionName) { animation-name : -ua-view-transition-group-anim-transitionName; } Note: The above code example contains variables to be replaced.
-
Set capturedElement’s image pair isolation rule to a new
CSSStyleRule
representing the following CSS, and append it to document’s view transition style sheet::root:
:view-transition-image-pair ( transitionName) { isolation : isolate; } Note: The above code example contains variables to be replaced.
-
Set capturedElement’s view blend mode rule to a new
CSSStyleRule
representing the following CSS, and append it to document’s view transition style sheet::root:
:view-transition-old ( transitionName), :root::view-transition-new ( transitionName) { mix-blend-mode : plus-lighter; } Note: The above code example contains variables to be replaced.
Note: mix-blend-mode: plus-lighter ensures that the blending of identical pixels from the old and new images results in the same color value as those pixels, and achieves a "correct" cross-fade.
Isolation and the dynamic setting of blending is only necessary to get the right cross-fade between new and old image pixels. Would it be simpler to always add it and try to optimize in the implementation?
-
-
8.4. Call the update callback
ViewTransition
transition:
Note: This is guaranteed to happen for every ViewTransition
,
even if the transition is skipped.
The reasons for this are discussed in § 1.3 Transitions as an enhancement.
-
Assert: transition’s phase is "
done
", or before "update-callback-called
". -
Let callbackPromise be null.
-
If transition’s update callback is null, then set callbackPromise to a promise resolved with undefined, in transition’s relevant Realm.
-
Otherwise, set callbackPromise to the result of invoking transition’s update callback.
-
If transition’s phase is not "
done
", then set transition’s phase to "update-callback-called
". -
Resolve transition’s update callback done promise with the result of reacting to callbackPromise:
-
If the promise was fulfilled, then return undefined.
Note: Since the rejection of callbackPromise isn’t explicitly handled here, if callbackPromise rejects, then transition’s update callback done promise will reject with the same reason.
-
8.5. Skip the view transition
ViewTransition
transition with reason reason:
-
Let document be transition’s relevant global object’s associated document.
-
Assert: document’s active view transition is transition.
-
If transition’s phase is before "
update-callback-called
", then queue a global task on the DOM manipulation task source, given transition’s relevant global object, to call the update callback of transition. -
Set transition suppressing rendering to false.
-
Clear view transition transition.
-
Set transition’s phase to "
done
". -
Reject transition’s ready promise with reason.
Note: The ready promise may already be resolved at this point, if
skipTransition()
is called after we start animating. In that case, this step is a no-op. -
Resolve transition’s finished promise with the result of reacting to transition’s update callback done promise:
-
If the promise was fulfilled, then return undefined.
Note: Since the rejection of transition’s update callback done promise isn’t explicitly handled here, if transition’s update callback done promise rejects, then transition’s finished promise will reject with the same reason.
-
8.6. Capture the image
-
If element is the document element, then:
-
Render the region of the element and the [/=document=]'s top layer that intersects the snapshot containing block, on a transparent canvas the size of the snapshot containing block, following the capture rendering characteristics, and these additional characteristics:
-
Areas outside element’s scrolling box should be rendered as if they were scrolled to, without moving or resizing the layout viewport. This must not trigger events related to scrolling or resizing, such as
IntersectionObserver
s.An example of what the user sees compared to the captured snapshot. This example assumes the root is the only element with a transition name. -
Areas that cannot be scrolled to (i.e. they are out of scrolling bounds), should render the canvas background.
An example of what the user sees compared to the captured snapshot. This example assumes the root is the only element with a transition name.
-
-
Return the canvas as an image. The natural size of the image is equal to the snapshot containing block.
-
-
Otherwise:
-
Render element and its descendants, at the same size it appears in its node document, over an infinite transparent canvas, following the capture rendering characteristics.
-
Return the portion of the canvas that includes element’s ink overflow rectangle as an image. The natural size of the image must be element’s border box's size, and its origin corresponds to the element’s border box's origin.
-
8.6.1. Capture rendering characteristics
-
The origin of element’s ink overflow rectangle is anchored to canvas origin.
-
If the referenced element has a transform applied to it (or its ancestors), then the transform is ignored.
Note: This transform is applied to the snapshot using the
transform
property of the associated ::view-transition-group pseudo-element. -
Effects on the element, such as opacity and filter are applied to the capture.
-
Implementations may clip the rendered contents if the ink overflow rectangle exceeds some implementation-defined maximum. However, the captured image should include, at the very least, the contents of element that intersect with the snapshot containing block. Implementations may adjust the rasterization quality to account for elements with a large ink overflow area that are transformed into view.
-
For each descendant of shadow-including descendant
Element
and pseudo-element of element, if descendant has a computed value of view-transition-name that is not none, then skip painting descendant.Note: This is necessary since the descendant will generate its own snapshot which will be displayed and animated independently.
Refactor this so the algorithm takes a set of elements that will be captured. This centralizes the logic for deciding if an element should be included or not.
Specify what happens with mix-blend-mode.
8.7. Handle transition frame
ViewTransition
transition:
-
Let document be transition’s relevant global object’s associated document.
-
Let hasActiveAnimations be a boolean, initially false.
-
For each element of transition’s transition root pseudo-element's inclusive descendants:
-
For each animation whose timeline is a document timeline associated with document, and contains at least one associated effect whose effect target is element, set hasActiveAnimations to true if any of the following conditions is true:
The prose around "effect target" is incorrect, but #8001 needs to land before it can be fixed.
-
animation’s play state is paused or running.
-
document’s pending animation event queue has any events associated with animation.
This prose isn’t quite right, but it’s blocked on #8004.
-
-
-
If hasActiveAnimations is false:
-
Set transition’s phase to "
done
". -
Clear view transition transition.
-
Resolve transition’s finished promise.
-
Return.
-
-
If transition’s initial snapshot containing block size is not equal to the snapshot containing block size, then skip the view transition for transition, and return.
-
Update pseudo-element styles for transition.
If failure is returned, then skip the view transition for transition with an "
InvalidStateError
"DOMException
in transition’s relevant Realm, and return.Note: The above implies that a change in incoming element’s size or position will cause a new keyframe to be generated. This can cause a visual jump. We could retarget smoothly but don’t have a use-case to justify the complexity. See issue 7813 for details.
8.8. Update pseudo-element styles
ViewTransition
transition:
-
For each transitionName → capturedElement of transition’s named elements:
-
Let width, height, transform, writingMode, and direction be null.
-
If capturedElement’s new element is null, then:
-
Set width to capturedElement’s old width.
-
Set height to capturedElement’s old height.
-
Set transform to capturedElement’s old transform.
-
Set writingMode to capturedElement’s old writing-mode.
-
Set direction to capturedElement’s old direction.
-
-
Otherwise:
-
Return failure if any of the following conditions is true:
-
capturedElement’s new element has a flat tree ancestor that skips its contents.
-
capturedElement’s new element is not rendered.
-
capturedElement has more than one box fragment.
Note: Other rendering constraints are enforced via capturedElement’s new element being involved in a view transition.
-
-
Set width to the current width of capturedElement’s new element's border box.
-
Set height to the current height of capturedElement’s new element's border box.
-
Set transform to a transform that would map capturedElement’s new element's border box from the snapshot containing block origin to its current visual position.
-
Set writingMode to the computed value of writing-mode on capturedElement’s new element.
-
Set direction to the computed value of direction on capturedElement’s new element.
-
-
If capturedElement’s group styles rule is null, then set capturedElement’s group styles rule to a new
CSSStyleRule
representing the following CSS, and append it to document’s view transition style sheet.Otherwise, update capturedElement’s group styles rule to match the following CSS:
:root:
:view-transition-group ( transitionName) { width : width; height : height; transform : transform; writing-mode : writingMode; direction : direction; } Note: The above code example contains variables to be replaced.
-
If capturedElement’s new element is not null, then:
-
Let new be the ::view-transition-new() with the view-transition name transitionName.
-
Set new’s replaced element content to the result of capturing the image of capturedElement’s new element.
-
-
This algorithm must be executed to update styles in user-agent origin if its effects can be observed by a web API.
Note: An example of such a web API is window.getComputedStyle(document.documentElement, "::view-transition")
.
8.9. Clear view transition
ViewTransition
transition:
-
Let document be transition’s relevant global object’s associated document.
-
Assert: document’s active view transition is transition.
-
For each capturedElement of transition’s named elements' values:
-
If capturedElement’s new element is not null, then set capturedElement’s new element's involved in a view transition to false.
-
For each style of capturedElement’s style definitions:
-
If style is not null, and style is in document’s view transition style sheet, then remove style from document’s view transition style sheet.
-
-
-
Set document’s show view-transition root pseudo-element to false.
-
Set document’s active view transition to null.
Privacy Considerations
This specification introduces no new privacy considerations.
Security Considerations
The images generated using capture the image algorithm could contain cross-origin data (if the Document is embedding cross-origin resources) or sensitive information like visited links. The implementations must ensure this data can not be accessed by the Document. This should be feasible since access to this data should already be prevented in the default rendering of the Document.
Appendix A. Changes
This appendix is informative.
Changes from 2022-05-25 Working Draft
-
Fix typo in ::view-transition-new user agend style sheet. See PR.
Changes from 2022-11-24 Working Draft
-
Pointer events resolve to the documentElement when rendering is suppressed. See issue 7797.
-
Add rendering constraints to elements participating in a transition. See issue 8139 and issue 7882.
-
Remove html specifics from UA stylesheet to support ViewTransitions on SVG Documents.
-
Rename updateDOMCallback to
UpdateCallback
. See issue 8144. -
Rename snapshot viewport to snapshot containing block.
-
Skip the transition if viewport size changes. See issue 8045.
-
Add support for :only-child. See issue 8057.
-
Add concept of a tree of pseudo-elements under pseudo-element root. See issue 8113.
-
When skipping a transition, the
UpdateCallback
is called in own task rather than synchronosly. See issue 7904 -
When capturing images, at least the in-viewport part of the image should be captured, downscale if needed. See issue 8561.
-
Applying the ink overflow to the captured image is implementation defined, and doesn’t affect the image’s natural size. See issue 8597.
-
Fragmented elements don’t participate in view transitions. See issue 8339.
-
Rename "snapshot root" to "snapshot containing block", and make it an absolute positioning containing block and a fixed positioning containing block for its descendants. See issue 8505.
Changes from 2022-10-25 Working Draft (FPWD)
-
Add view transition style sheet concept for dynamically generated UA styles scoped to the current Document.
-
Add snapshot viewport concept. See issue 7859.
-
Clarify timimg for resolving/rejecting promises when skipping the transition. See issue 7956.
-
Elements under a content-visiblity:auto element that skips its contents are ignored. See issue 7874.
-
UA styles on the pseudo-DOM stay in sync with author DOM for any developer observable API. See issue 7812.
-
Suppress rendering during updateCallback. See issue 7784.
-
Changes in size/position of elements in the new Document generate new UA animation keyframes. See issue 7813.
-
Scope keyframes to user agent stylesheets using -ua- prefix. See issue 7560.
-
Update pseudo element names to view-transition*. See issue 7960.
-
Update selector syntax for pseudo-elements. See issue 7788.
-
Add sections for security/privacy considerations.