The View Transitions API provides a mechanism for easily creating animated transitions between different DOM states while also updating the DOM contents in a single step.
Concepts and usage
View transitions are a popular design choice for reducing users' cognitive load, helping them stay in context, and reducing perceived loading latency as they move between states or views of an application.
However, creating view transitions on the web has historically been difficult. Transitions between states in single-page apps (SPAs) tend to involve writing significant CSS and JavaScript to:
- Handle the loading and positioning of the old and new content.
- Animate the old and new states to create the transition.
- Stop accidental user interactions with the old content from causing problems.
- Remove the old content once the transition is complete.
Accessibility issues like loss of reading position, focus confusion, and strange live region announcement behavior can also result from having the new and old content both present in the DOM at once. In addition, cross-document view transitions (i.e. across different pages in regular, non-SPA websites) are impossible.
The View Transitions API provides a much easier way of handling the required DOM changes and transition animations.
Note: The View Transitions API doesn't currently enable cross-document view transitions, but this is planned for a future level of the spec and is actively being worked on.
Creating a basic view transition
As an example, an SPA may include functionality to fetch new content and update the DOM in response to an event of some kind, such as a navigation link being clicked or an update being pushed from the server. In our Basic View Transitions demo we've simplified this to a displayNewImage()
function that shows a new full-size image based on the thumbnail that was clicked. We've encapsulated this inside an updateView()
function that only calls the View Transition API if the browser supports it:
function updateView(event) {
const targetIdentifier = event.target.firstChild || event.target;
const displayNewImage = () => {
const mainSrc = `${targetIdentifier.src.split("_th.jpg")[0]}.jpg`;
galleryImg.src = mainSrc;
galleryCaption.textContent = targetIdentifier.alt;
};
if (!document.startViewTransition) {
displayNewImage();
return;
}
const transition = document.startViewTransition(() => displayNewImage());
}
This code is enough to handle the transition between displayed images. Supporting browsers will show the change from old to new images and captions as a smooth cross-fade (the default view transition). It will still work in non-supporting browsers but without the nice animation.
It is also worth mentioning that startViewTransition()
returns a ViewTransition
instance, which includes several promises, allowing you to run code in response to different parts of the view transition process being reached.
The view transition process
Let's walk through how this works:
- When
document.startViewTransition()
is called, the API takes a screenshot of the current page. - Next, the callback passed to
startViewTransition()
is invoked, in this case displayNewImage
, which causes the DOM to change. When the callback has run successfully, the ViewTransition.updateCallbackDone
promise fulfills, allowing you to respond to the DOM updating. - The API captures the new state of the page as a live representation.
- The API constructs a pseudo-element tree with the following structure:
::view-transition
└─ ::view-transition-group(root)
└─ ::view-transition-image-pair(root)
├─ ::view-transition-old(root)
└─ ::view-transition-new(root)
When the transition animation is about to run, the ViewTransition.ready
promise fulfills, allowing you to respond by running a custom JavaScript animation instead of the default, for example. - The old page view animates from
opacity
1 to 0, while the new view animates from opacity
0 to 1, which is what creates the default cross-fade. - When the transition animation has reached its end state, the
ViewTransition.finished
promise fulfills, allowing you to respond.
Different transitions for different elements
At the moment, all of the different elements that change when the DOM updates are transitioned using the same animation. If you want different elements to animate differently from the default "root" animation, you can separate them out using the view-transition-name
property. For example:
figcaption {
view-transition-name: figure-caption;
}
We've given the <figcaption>
element a view-transition-name
of figure-caption
to separate it from the rest of the page in terms of view transitions.
With this CSS applied, the pseudo-element tree will now look like this:
::view-transition
├─ ::view-transition-group(root)
│ └─ ::view-transition-image-pair(root)
│ ├─ ::view-transition-old(root)
│ └─ ::view-transition-new(root)
└─ ::view-transition-group(figure-caption)
└─ ::view-transition-image-pair(figure-caption)
├─ ::view-transition-old(figure-caption)
└─ ::view-transition-new(figure-caption)
The existence of the second set of pseudo-elements allows separate view transition styling to be applied just to the <figcaption>
. The different old and new page view captures are handled completely separate from one another.
The value of view-transition-name
can be anything you want except for none
— the none
value specifically means that the element will not participate in a view transition.
Note: view-transition-name
must be unique. If two rendered elements have the same view-transition-name
at the same time, ViewTransition.ready
will reject and the transition will be skipped.
Customizing your animations
The View Transitions pseudo-elements have default CSS Animations applied (which are detailed in their reference pages).
Notably, transitions for height
, width
, position
, and transform
do not use the smooth cross-fade animation. Instead, height and width transitions apply a smooth scaling animation. Meanwhile, position and transform transitions apply smooth movement animations to the element.
You can modify the default animation in any way you want using regular CSS.
For example, to change the speed of it:
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.5s;
}
Let's look at something more interesting — a custom animation for the <figcaption>
:
@keyframes grow-x {
from {
transform: scaleX(0);
}
to {
transform: scaleX(1);
}
}
@keyframes shrink-x {
from {
transform: scaleX(1);
}
to {
transform: scaleX(0);
}
}
::view-transition-old(figure-caption),
::view-transition-new(figure-caption) {
height: auto;
right: 0;
left: auto;
transform-origin: right center;
}
::view-transition-old(figure-caption) {
animation: 0.25s linear both shrink-x;
}
::view-transition-new(figure-caption) {
animation: 0.25s 0.25s linear both grow-x;
}
Here we've created a custom CSS animation and applied it to the ::view-transition-old(figure-caption)
and ::view-transition-new(figure-caption)
pseudo-elements. We've also added a number of other styles to both to keep them in the same place and stop the default styling from interfering with our custom animations.
Note that we also discovered another transition option that is simpler and produced a nicer result than the above. Our final <figcaption>
view transition ended up looking like this:
figcaption {
view-transition-name: figure-caption;
}
::view-transition-old(figure-caption),
::view-transition-new(figure-caption) {
height: 100%;
}
This works because, by default, ::view-transition-group
transitions width and height between the old and new views. We just needed to set a fixed height
on both states to make it work.
Controlling animations with JavaScript
The document.startViewTransition()
method returns a ViewTransition
object instance, which contains several promise members allowing you to run JavaScript in response to different states of the transition being reached. For example, ViewTransition.ready
fulfills once the pseudo-element tree is created and the animation is about to start, whereas ViewTransition.finished
fulfills once the animation is finished, and the new page view is visible and interactive to the user.
For example, the following JavaScript could be used to create a circular reveal view transition emanating from the position of the user's cursor on click, with animation provided by the Web Animations API.
let lastClick;
addEventListener("click", (event) => (lastClick = event));
function spaNavigate(data) {
if (!document.startViewTransition) {
updateTheDOMSomehow(data);
return;
}
const x = lastClick?.clientX ?? innerWidth / 2;
const y = lastClick?.clientY ?? innerHeight / 2;
const endRadius = Math.hypot(
Math.max(x, innerWidth - x),
Math.max(y, innerHeight - y),
);
const transition = document.startViewTransition(() => {
updateTheDOMSomehow(data);
});
transition.ready.then(() => {
document.documentElement.animate(
{
clipPath: [
`circle(0 at ${x}px ${y}px)`,
`circle(${endRadius}px at ${x}px ${y}px)`,
],
},
{
duration: 500,
easing: "ease-in",
pseudoElement: "::view-transition-new(root)",
},
);
});
}
This animation also requires the following CSS, to turn off the default CSS animation and stop the old and new view states from blending in any way (the new state "wipes" right over the top of the old state, rather than transitioning in):
::view-transition-image-pair(root) {
isolation: auto;
}
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
display: block;
}
Interfaces
ViewTransition
-
Represents a view transition, and provides functionality to react to the transition reaching different states (e.g. ready to run the animation, or animation finished) or skip the transition altogether.
Extensions to other interfaces
Document.startViewTransition()
-
Starts a new view transition and returns a ViewTransition
object to represent it.
CSS additions
Properties
view-transition-name
-
Provides the selected element with a separate identifying name and causes it to participate in a separate view transition from the root view transition — or no view transition if the none
value is specified.
Pseudo-elements
::view-transition
-
The root of the view transitions overlay, which contains all view transitions and sits over the top of all other page content.
::view-transition-group()
-
The root of a single view transition.
::view-transition-image-pair()
-
The container for a view transition's old and new views — before and after the transition.
::view-transition-old()
-
A static screenshot of the old view, before the transition.
::view-transition-new()
-
A live representation of the new view, after the transition.
Examples
Specifications
Browser compatibility
|
Desktop |
Mobile |
|
Chrome |
Edge |
Firefox |
Internet Explorer |
Opera |
Safari |
WebView Android |
Chrome Android |
Firefox for Android |
Opera Android |
Safari on IOS |
Samsung Internet |
View_Transitions_API |
111 |
111 |
No |
No |
97 |
No |
111 |
111 |
No |
No |
No |
22.0 |
See also