1. Introduction
This section is non-normative.
This specification introduces a DOM API and associated CSS features that allow developers to create animated visual transitions, called view transitions between different states of a document.
1.1. Separating Visual Transitions from DOM Updates
Traditionally, creating a visual transition between two document states required a period where both states were present in the DOM at the same time. In fact, it usually involved creating a specific DOM structure that could represent both states. For example, if one element was “moving” between containers, that element often needed to exist outside of either container for the period of the transition, to avoid clipping from either container or their ancestor elements.
This extra in-between state often resulted in UX and accessibility issues, as the structure of the DOM was compromised for a purely-visual effect.
View Transitions avoid this troublesome in-between state by allowing the DOM to switch between states instantaneously, then performing a customizable visual transition between the two states in another layer, using a static visual capture of the old state, and a live capture of the new state. These captures are represented as a tree of pseudo-elements (detailed in § 3.2 View Transition Pseudo-elements), where the old visual state co-exists with the new state, allowing effects such as cross-fading while animating from the old to new size and position.
1.2. View Transition Customization
By default, document.
creates a view transition consisting of
a page-wide cross-fade between the two DOM states.
Developers can also choose which elements are captured independently
using the view-transition-name CSS property,
allowing these to be animated independently of the rest of the page.
Since the transitional state (where both old and new visual captures exist)
is represented as pseudo-elements,
developers can customize each transition using familiar features
such as CSS Animations
and Web Animations.startViewTransition()
1.3. View Transition Lifecycle
A successful view transition goes through the following phases:
-
Developer calls
document., which returns astartViewTransition(updateCallback)ViewTransition, viewTransition. -
Current state captured as the “old” state.
-
Rendering paused.
-
Developer’s
updateCallbackfunction, if provided, is called, which updates the document state. -
viewTransition.fulfills.updateCallbackDone -
Current state captured as the “new” state.
-
Transition pseudo-elements created. See § 3.2 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.4. Transitions as an enhancement
A key part of the View Transition API design
is that an animated transition is a visual enhancement
to an underlying document state change.
That means a failure to create a visual transition,
which can happen due to misconfiguration or device constraints,
will not prevent the developer’s ViewTransitionUpdateCallback being called,
even if it’s known in advance that the transition animations cannot happen.
For example, if the developer calls skipTransition() at the start of the view transition lifecycle,
the steps relating to the animated transition,
such as creating the view transition tree,
will not happen.
However, the ViewTransitionUpdateCallback will still be called.
It’s only the visual transition that’s skipped,
not the underlying state change.
Note: If the DOM change should also be skipped,
then that needs to be handled by another feature.
is an example of a feature developers could use to handle this.navigateEvent.signal
Although the View Transition API allows DOM changes to be asynchronous via the ViewTransitionUpdateCallback,
the API is not responsible for queuing or otherwise scheduling DOM changes
beyond any 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.5. Rendering Model
View Transition works by replicating an element’s rendered state using UA generated pseudo-elements. Aspects of the element’s rendering which apply to the element itself or its descendants, for example visual effects like filter or opacity and clipping from overflow or clip-path, are applied when generating its image in Capture the image.
However, properties like mix-blend-mode which define how the element draws when it is embedded can’t be applied to its image. Such properties are applied to the element’s corresponding ::view-transition-group() pseudo-element, which is meant to generate a box equivalent to the element.
If the ::view-transition-group() has a corresponding element in the "new" states, the browser keeps the properties copied over to the ::view-transition-group() in sync with the DOM element in the "new" state. If the ::view-transition-group() has corresponding elements both in the "old" and "new" state, and the property being copied is interpolatable, the browser also sets up a default animation to animate the property smoothly.
1.6. Examples
function spaNavigate( data) { updateTheDOMSomehow( data); }
A view 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. Tagging Individually Transitioning Subtrees: the view-transition-name property
| 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 |
Note: though view-transition-name is discretely animatable, animating it doesn’t affect the running view transition. Rather, it’s a way to set its value in a way that can change over time or based on a timeline. An example for using this would be to change the view-transition-name based on scroll-driven animations.
The view-transition-name property “tags” an element for capture in a view transition, tracking it independently in the view transition tree under the specified view transition name. An element so captured is animated independently of the rest of the page.
- none
-
The element will not participate independently in a view transition.
- <custom-ident>
-
The element participates independently in a view transition—as either an old or new element—with the specified view transition name.
Each view transition name is a tree-scoped name.
Note: Since currently only document-scoped view transitions are supported, only view transition names that are associated with the document are respected.
The values none, auto, and match-element are excluded from <custom-ident> here.
Note: If this name is not unique (i.e. if two elements simultaneously specify the same view transition name) then the view transition will abort.
Note: For the purposes of this API, if one element has view transition name foo in the old state, and another element has view transition name foo in the new state, they are treated as representing different visual state of the same element, and will be paired in the view transition tree. 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.
If the element’s principal box is fragmented, skipped, or not rendered, this property has no effect. See § 7 Algorithms for exact details.
Element element:
-
Let scopedViewTransitionName be the computed value of view-transition-name for element.
-
If scopedViewTransitionName is associated with element’s node document, then return scopedViewTransitionName.
-
Otherwise, return none.
2.1.1. Rendering Consolidation
Elements captured in a view transition during a view transition or whose view-transition-name computed value is not none (at any time):
-
Form a stacking context.
-
Form a backdrop root.
3. Pseudo-elements
3.1. Pseudo-element Trees
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-transition-image-pair(ident) contains a single child.
As in, there is no sibling ::view-transition-old(ident).
3.2. View Transition Pseudo-elements
The visualization of a view transition is represented as a pseudo-element tree called the view transition tree composed of the view transition pseudo-elements defined below. This tree is built during the setup transition pseudo-elements step, and is rooted under a ::view-transition pseudo-element originating from the root element. All of the view transition pseudo-elements are selected from their ultimate originating element, the document element.
The view transition tree is not exposed to the accessibility tree.
::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.
3.2.1. Named View Transition Pseudo-elements
Several of the view transition pseudo-elements are named view transition pseudo-elements, which are functional tree-abiding view transition pseudo-elements associated with a view transition name. These pseudo-elements take a <pt-name-selector> as their argument, and their syntax follows the pattern:
::view-transition-pseudo(<pt-name-selector>)
where <pt-name-selector> selects a view transition name, and has the following syntax definition:
<pt-name-selector> = '*' | <custom-ident>
A named view transition pseudo-element selector only matches a corresponding pseudo-element if its <pt-name-selector> matches that pseudo-element’s view transition name, i.e. if it is either * or a matching <custom-ident>.
Note: The view transition name of a view transition pseudo-element is set to the view-transition-name that triggered its creation.
The specificity of a named view transition pseudo-element selector with a <custom-ident> argument is equivalent to a type selector. The specificity of a named view transition pseudo-element selector with a * argument is zero.
3.2.2. View Transition Tree Root: the ::view-transition pseudo-element
The ::view-transition pseudo-element is a tree-abiding pseudo-element that is also a pseudo-element root. Its originating element is the document’s document element, and its containing block is the snapshot containing block.
Note: This element serves as the parent of all ::view-transition-group() pseudo-elements.
3.2.3. View Transition Named Subtree Root: the ::view-transition-group() pseudo-element
The ::view-transition-group() pseudo-element is a named view transition pseudo-element that represents a matching named view transition capture. A ::view-transition-group() pseudo-element is generated for each view transition name as a child of the ::view-transition pseudo-element, and contains a corresponding ::view-transition-image-pair().
If there’s both an “old” and “new” state, styles in the dynamic 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.
3.2.4. View Transition Image Pair Isolation: the ::view-transition-image-pair() pseudo-element
The ::view-transition-image-pair() pseudo-element is a named view transition pseudo-element that represents a pair of corresponding old/new view transition captures. This pseudo-element is a child of the corresponding ::view-transition-group() pseudo-element and contains a corresponding ::view-transition-old() pseudo-element and/or a corresponding ::view-transition-new() pseudo-element (in that order).
3.2.5. View Transition Old State Image: the ::view-transition-old() pseudo-element
The ::view-transition-old() pseudo-element is an empty named view transition pseudo-element that represents a visual snapshot of the “old” state as a replaced element; it is omitted if there’s no “old” state to represent. Each ::view-transition-old() pseudo-element is a child of the corresponding ::view-transition-image-pair() pseudo-element.
The appearance of this element can be manipulated with object-* properties in the same way that other replaced elements can be.
Note: The content and natural dimensions of the image are captured in capture the image, and set in setup transition pseudo-elements.
Note: Additional styles in the dynamic view transition style sheet added to animate these pseudo-elements are detailed in setup transition pseudo-elements and update pseudo-element styles.
3.2.6. View Transition New State Image: the ::view-transition-new() pseudo-element
The ::view-transition-new() pseudo-element (like the analogous ::view-transition-old() pseudo-element) is an empty named view transition pseudo-element that represents a visual snapshot of the “new” state as a replaced element; it is omitted if there’s no “new” state to represent. Each ::view-transition-new() pseudo-element is a child of the corresponding ::view-transition-image-pair() pseudo-element.
Note: The content and natural dimensions of the image are captured in capture the image, then set and updated in setup transition pseudo-elements and update pseudo-element styles.
4. View Transition Layout
The view transition pseudo-elements are styled, laid out, and rendered like normal elements, except that they originate in the snapshot containing block rather than the initial containing block and are painted in the view transition layer above the rest of the document.
4.1. The Snapshot Containing Block
The snapshot containing block is a rectangle that covers all areas of the window that could potentially display page content (and is therefore consistent regardless of root scrollbars or interactive widgets). This makes it likely to be consistent for the document element’s old image and new element.
Within a child navigable, the snapshot containing block is the union of the navigable’s viewport with any scrollbar gutters.