The View Transition API
by
There are two basic types of view transitions. Single-page transitions change the structure of a page without changing the location, like opening a dialog box or navigating pages of a typical SPA. Cross-document transitions happen when the document is replaced as a whole and a new browser page is loaded.
Traditionally, single-page transitions were hard to implement and only conveniently usable when utilizing a JS framework that provides a nice and easy high-level API. Cross-document transitions were technically impossible until now, but browsers recently started to implement the View Transition API, providing a new method for both cross-document and single-page (on-page) DOM transitions.
Cross-document view transitions can be enabled using a single CSS at-rule.
@view-transition {
navigation: auto;
}
The transition only works if both the current and the next page are on the same origin and both opt-in to the cross-document view transition using the CSS from above. The browser will create a ViewTransition
instance and a snapshot image of the current page.
The browser then adds a ::view-transition
pseudo-element to the top of the root element of the new page. This pseudo-element contains a static snapshot image of the old page and an interactive DOM region for the new page. All view transitions create a simple cross-fade by default. This simplifies the setup a lot. For most cases a cross-fade should be perfectly fine.
The Chrome devs published some example videos on their view transitions article.
Custom Animations
The animations can be changed using CSS pseudo-selectors and @keyframes
sequences.
@keyframes slide-out {
from {
transform: translateX(0%);
}
to {
transform: translateX(-100%);
}
}
@keyframes slide-in {
from {
transform: translateX(100%);
}
to {
transform: translateX(0%);
}
}
::view-transition-group(root) {
animation-duration: .3s;
}
::view-transition-old(root) {
animation-name: slide-out;
}
::view-transition-new(root) {
animation-name: slide-in;
}
root
is the name of the view-transition added to the root element by default. It’s also possible to target other view-transition elements, change the root transition name or target all transitions using *
. The browser always constructs the following tree structure inside the ::view-transition
root element.
::view-transition
├─ ::view-transition-group(name)
│ └─ ::view-transition-image-pair(name)
│ ├─ ::view-transition-old(name)
│ └─ ::view-transition-new(name)
└─ …other groups…
Each view transition creates a group inside the root pseudo-element. Each group has an image pair with the old and new view transition pseudo-elements.
Single Element Transitions
Transitioning a single element’s DOM changes is relatively easy. The document updates have to be done inside the document.startViewTransition(...)
callback function parameter. startViewTransition
returns a ViewTransition
object that contains some promises that can be awaited, like viewTransition.finished
, to run code after the transition finishes.
The tricky part is that, if a cross-document transition is enabled, the browser will also do a cross-fade on the DOM regions that are outside the transitioned element. In the following example I had to disable that manually by temporarily disabling the root transition. I also had to use a regular CSS transition for the wrapper height, since the view-transition was only working for the child element.
document.documentElement.style.viewTransitionName = 'none';
await document
.startViewTransition(() => {/* ... */})
.finished;
document.documentElement.style.viewTransitionName = 'root';
Demo: Clicking on the red div removes it from the parent container and adds a new div to the opposite container.
Sorry, your browser doesn't support the View Transition API .
The position, color and size transitions are all handled automatically. The browser can find the new element with the same view-transition-name
and create a transition matrix to animate between the two positions, even if it’s moved across parent containers.
The relevant parts for the view-transition are the JS code to handle the DOM update and the CSS to register the view-transition-name
.
document.startViewTransition(() => {
// DOM changes
});
#demo-1-block {
view-transition-name: view-transition-demo-1;
&.small {
width: 100px;
height: 100px;
background: red;
}
&.large {
width: 200px;
height: 200px;
background: blue;
}
}
Transitioning Multiple Elements at Once
Creating a chain of staggered view transitions requires a much more complex setup, both in JS and CSS. Each staggered item needs a unique view-transition-name
and the pseudo-element selectors require creating styles for each unique view-transition.
Demo: Clicking on the red div adds four more elements to the right of it. The elements use a delayed transition animation to create a staggered effect.
Sorry, your browser doesn't support the View Transition API .
The view-transition-class
CSS property can be used for styling multiple view transitions, but that didn’t quite work for me and I used the following SASS code to generate the CSS for a group of transitions.
@for $i from 0 through 4 {
::view-transition-group(view-transition-demo-1-#{$i+1}) {
animation-duration: .2s;
}
::view-transition-new(view-transition-demo-1-#{$i+1}) {
animation-name: transition-slide-in;
animation-delay: $i * .1s;
}
::view-transition-old(view-transition-demo-1-#{$i+1}) {
animation-name: transition-slide-out;
animation-delay: (4 - $i) * .1s;
}
}
Triggering the view transitions required similar loops in JavaScript.
document.startViewTransition(() => {
for (let i = 1; i <= 5; i++) {
const div = document.createElement("div");
div.classList.add("block");
div.style.viewTransitionName = `view-transition-demo-1-${i}`;
parent.appendChild(div);
}
});
Browser Compatibility
As of right now all major browsers except Firefox support the View Transition API. The Firefox devs are working on it and the feature will hopefully be released soon.
Until then, browser compatibility can be checked by wrapping the DOM updates with the view-transition function conditionally.
if ('startViewTransition' in document) {
document.startViewTransition(() => {
// ... update DOM with transition
});
} else {
// ... update DOM without transition
}
Framework Support
Looking at some of the major frameworks shows that view-transition support seems to be on a good trajectory.
- React has experimental support with React Router already supporting view transitions
- Next.js provides a flag to enable experimental support
- Astro was one of the first major web frameworks to mainstream view transitions and provides many additional features like persisting elements and forwards/backwards navigation animations
- SvelteKit supports view transitions and posted an article with examples
The big picture seems to be that view transitions are being adopted by all major browsers and frameworks. I’m very glad to finally see a standardized way of creating transition animations making its way across browsers and frameworks. I didn’t have to think twice to enable the cross-document default transition on this blog. Now I only have to wait for Firefox support to be able to enjoy it myself.