# Introduction # {#intro}
*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=].
## Separating Visual Transitions from DOM Updates ## {#separating-transitions}
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 [[#view-transition-pseudos]]),
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.
## View Transition Customization ## {#customizing}
By default, document.{{Document/startViewTransition()}}
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.
## View Transition Lifecycle ## {#lifecycle}
A successful [=view transition=] goes through the following phases:
1. Developer calls document.{{Document/startViewTransition}}({{UpdateCallback|updateCallback}}),
which returns a {{ViewTransition}}, viewTransition.
1. Current state captured as the “old” state.
1. Rendering paused.
1. Developer's {{UpdateCallback|updateCallback}} function, if provided, is called,
which updates the document state.
1. viewTransition.{{ViewTransition/updateCallbackDone}} fulfills.
1. Current state captured as the “new” state.
1. Transition pseudo-elements created.
See [[#view-transition-pseudos]] for an overview of this structure.
1. Rendering unpaused, revealing the transition pseudo-elements.
1. viewTransition.{{ViewTransition/ready}} fulfills.
1. Pseudo-elements animate until finished.
1. Transition pseudo-elements removed.
1. viewTransition.{{ViewTransition/finished}} fulfills.
## Transitions as an enhancement ## {#transitions-as-enhancements}
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 {{UpdateCallback}} being called,
even if it's known in advance that the transition animations cannot happen.
For example, if the developer calls {{ViewTransition/skipTransition()}} at the start of the [[#lifecycle|view transition lifecycle]],
the steps relating to the animated transition,
such as creating the [=view transition tree=],
will not happen.
However, the {{UpdateCallback}} 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.
{{NavigateEvent|navigateEvent}}.{{NavigateEvent/signal}} is an example of a feature developers could use to handle this.
Although the View Transition API allows DOM changes to be asynchronous via the {{UpdateCallback}},
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.
## Rendering Model ## {#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 a corresponding 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.
## Examples ## {#examples}
Taking a page that already updates its content using a pattern like this:
```js
function spaNavigate(data) {
updateTheDOMSomehow(data);
}
```
A [=view transition=] could be added like this:
```js
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 [[#view-transition-pseudos|tree of pseudo-elements]],
so customizations can be made using CSS. For example:
```css
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 5s;
}
```
This results in a slower transition:
Building on the previous example, motion can be added:
```css
@keyframes fade-in {
from { opacity: 0; }
}
@keyframes fade-out {
to { opacity: 0; }
}
@keyframes slide-from-right {
from { transform: translateX(30px); }
}
@keyframes slide-to-left {
to { transform: translateX(-30px); }
}
::view-transition-old(root) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}
::view-transition-new(root) {
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}
```
Here's the result:
Building on the previous example,
the header and text within the header can be given their own ''::view-transition-group()''s for the transition:
```css
.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:
Building on the previous example, let's say some pages have a sidebar:
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:
```css
.sidebar {
view-transition-name: sidebar;
}
@keyframes slide-to-right {
to { transform: translateX(30px); }
}
/* Entry transition */
::view-transition-new(sidebar):only-child {
animation: 300ms cubic-bezier(0, 0, 0.2, 1) both fade-in,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}
/* Exit transition */
::view-transition-old(sidebar):only-child {
animation: 150ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms 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.
Not building from previous examples this time,
let's say we wanted to create a circular reveal from the user's cursor.
This can't be done with CSS alone.
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:
```css
::view-transition-image-pair(root) {
isolation: auto;
}
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
}
```
Then, the JavaScript:
```js
// 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:
Name: view-transition-name
Value: none | <>
Initial: none
Inherited: no
Percentages: n/a
Computed Value: as specified
Animation type: discrete
Note: though 'view-transition-name' is [=discrete|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.
: <>
:: 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 and auto are excluded from <> 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=],
[=skips its contents|skipped=],
or [=element-not-rendered|not rendered=],
this property has no effect.
See [[#algorithms]] for exact details.
To get the document-scoped view transition name for an {{Element}} |element|:
1. Let |scopedViewTransitionName| be the [=computed value=] of 'view-transition-name' for |element|.
1. If |scopedViewTransitionName| is associated with |element|'s [=node document=], then return |scopedViewTransitionName|.
1. Otherwise, return ''view-transition-name/none''.
### Rendering Consolidation ### {#named-and-transitioning}
[=/Elements=] [=captured in a view transition=] during a [=view transition=]
or whose 'view-transition-name' [=computed value=] is not ''view-transition-name/none'' (at any time):
- Form a [=stacking context=].
- Are [[css-transforms-2#grouping-property-values|flattened in 3D transforms]].
- Form a [=backdrop root=].
# Pseudo-elements # {#pseudo}
## Pseudo-element Trees ## {#pseudo-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 [=tree/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 [=tree/descendant=] [=tree-abiding pseudo-elements=].
When a [=pseudo-element=] [=tree/participates=] in a [=pseudo-element tree=],
its [=originating pseudo-element=] is its [=tree/parent=].
If a [=tree/descendant=] |pseudo| of a [=pseudo-element root=] has no other [=tree/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 [=tree/child=].
As in, there is no [=tree/sibling=] `::view-transition-old(ident)`.
## View Transition Pseudo-elements ## {#view-transition-pseudos}
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 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.
For example,
the ''::view-transition-group()'' pseudo-element is attached to the root element selector directly,
as in '':root::view-transition-group()'';
it is not attached to its parent, the ''::view-transition'' pseudo-element.
Once the user-agent has captured both the “old” and “new” states of the document,
it creates a structure of pseudo-elements like the following:
```
::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 [[#ua-styles|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.
### Named View Transition Pseudo-elements ### {#named-view-transition-pseudo}
Several of the [=view transition pseudo-elements=]
are named view transition pseudo-elements,
which are [=functional pseudo-element|functional=] [=tree-abiding pseudo-element|tree-abiding=] [=view transition pseudo-elements=]
associated with a [=view transition name=].
These pseudo-elements take a <> as their argument,
and their syntax follows the pattern: