Last updated: First published:
View Transition JavaScript API
Browser-native cross-document view transitions can be created entirely with CSS, without the need for JavaScript. However, even for cross-document view transitions, JavaScript becomes essential for advanced functionality. And for same-document view transitions, even the activation is done via JavaScript:
- Triggering same-page view transitions with
startViewTransition()
, - Dynamically modifying CSS properties like
view-transition-name
, - Defining view transition types,
- Executing code at the start or end of a transition.
Same document view transitions are amazing! You assign view transition names to the HTML elements you want to animate during the transition. The transition spans from the current DOM state to a future version, which is generated by calling and awaiting an update function.
You start a same-document view transition with an update callback that transforms the current DOM to the future state. The View Transition API takes care of creating and running animations that smoothly transform the old state into the new one.
It is good practice to care for users that do not want motion effects and for browsers that do not support the API:
if (window.matchMedia('(prefers-reduced-motion: reduce)') || !document.startViewTransition) { updateCallback()} else { document.startViewTransition(updateCallback);}
For users who prefer reduced motion or when the browser doesn’t support the View Transition API, the code above directly executes the update function without calling startViewTransition()
.
The update callback can either be a synchronous or asynchronous function. If it’s asynchronous, the View Transition API will wait for it to settle. However, you should aim to spend only little time inside the callback. For one thing, the browser freezes the renderer while the update callback executes. And browsers may impose time limits. For Chrome this seem to be 4 seconds before it loses its temper. If you need an expensive preparation for the transition, like loading and parsing a new DOM from file, you better do that before you call startViewTransition()
. The update callback is optional, so you can even transition to the same version of the DOM by omitting the update function, which might make sense with custom animations.
You can also pass an options object to startViewTransition()
to define view transition types that should be active during the transition.
document.startViewTransition({ types: ["boom", "backward"], update: changeTheDOM,});
Here is the full signature for startViewTransition
type UpdateCallback = undefined | (() => void | Promise<void>);type StartViewTransitionOptions = { types?: string[] | Set<string>; update?: UpdateCallback;};
interface Document { startViewTransition( param?: StartViewTransitionOptions | UpdateCallback ): ViewTransition;}
The startViewTransition()
function returns an object that includes promises, the current transition types, and a function to skip the animations.
interface ViewTransition { readonly updateCallbackDone: Promise<undefined>; readonly ready: Promise<undefined>; readonly finished: Promise<undefined>; readonly types: Set<string>; skipTransition(): void;}
The promises allow you to hook into the different stages of the view transition process.
-
The
updateCallbackDone
promise resolves once the update callback fulfills. You can use this to (synchronously) make last-minute changes before the new images are created. If the update function rejects, theupdateCallbackDone
promise rejects, too. View transition processing ends, and theready
andfinished
promises also reject. -
The
ready
promise settles once the pseudo-elements have been inserted into the DOM. Inserting the pseudo-elements triggers their animations. For example, you can use this promise to add your own JavaScript animations using the Web Animation API. If there are errors, like duplicate view transition names, theready
promise will reject. -
Lastly, the
finished
promise settles once the animations are complete and the pseudo-elements have been removed. It resolves with the same outcome asupdateCallbackDone
.
When using the promises, your call to startViewTransition()
might look something like this:
if (document.startViewTransition && window.matchMedia('(prefers-reduced-motion: no-preference)')) { const viewTransition = document.startViewTransition({update: updateCallback, types: ...}); viewTransition.updateCallbackDone.catch(err => ... error handling ...); viewTransition.ready.catch(err => ... error handling ...); viewTransition.ready.then(() => ... trigger parallel animation ...); viewTransition.finished.finally(() => ... clean up ...)} else { updateCallback();}
Calling skipTransition()
stops the current view transition animations or prevents their start if the ready
promise hasn’t settled yet.
If called before or during the update callback, it doesn’t affect the update itself, nor does it impact the processing or the outcome of the updateCallbackDone promise.
Skipping the transition means that the pseudo-element tree created by the view transition API is removed (or never inserted). Without the pseudo-elements, the browser directly shows the new DOM state. Therefore a skipped view transitions looks like the animation would jump abruptly to its end state.
You can dynamically add or remove view transition types during the view transition by modifying the types
object. Such changes will directly affect CSS rules that use the :active-view-transition-type
pseudo-class.
View transition types are a feature of Level 2 of the View Transition API. They can be polyfilled if you want to use them with Level 1 implementations.
Calling startViewTransition()
while another view transition is active will interrupt the current view transition, fast forward to the end state of the animations, and start the new view transition from there.
If you want to avoid these jerky jump, you can either
- chain the update callback functions, passing the chained callback to a single call of
startViewTransition()
, or - chain multiple calls to
startViewTransition()
by awaiting thefinished
promise before starting the next view transition.
The mayStartViewTransition()
function from the Utensil Drawer has automatic support for both strategies.
Scoped view transitions↗ are rooted at arbitrary DOM elements. Instead of calling document.startViewTransition()
it’s element.startViewTransition()
. This has some severe advantages:
- Rendering is only blocked on the DOM subtree rooted at element. The rest of the page is still interactive.
- There can be several scoped view transitions on the page and they can independently perform in parallel.
Rumor has it that the root element of the scoped view transition needs to have its contain
CSS property set to layout
(or strict
).
Scoped view transitions are not widely supported jet. If your browser supports them, this site features a small example on clipping.
You activate cross-document view transitions by adding the following incantation to the styles of all pages that should participate in view transitions:
@view-transition{ navigation: auto;}
Opting in to cross-document view transitions is CSS-only. But cross-document view transitions, too, have their JavaScript API.
Cross-document view transitions work similarly to same-document ones but are triggered by navigation. In particular you can get access to the view transition object in event listeners for the new pageswap
and pagereveal
events.
Two key events enable JavaScript interaction: pageswap
, dispatched just before leaving the old page, and pagereveal
, dispatched before the new page renders.
Both events include a viewTransition
property, which holds a ViewTransition
object if the transition is part of a cross-document view transition. The object has the same structure as for same-document view transitions.
For pageswap
, if viewTransition
is defined, its promises will never settle because the page is unloaded before it is “updated”.
For pagereveal
, if viewTransition
is defined, its updateCallbackDone
promise is immediately settled.
The events allow you to manipulate the DOM or CSS properties right before snapshots are taken or the new page’s live images are captured. In particular, they allow for setting view transition types for the current view transition.
In a pageswap
listener, you can define view transition types by adding them to event.viewTransition.types
, which are then only applied to the old page. The types can be used to control via CSS what view transition names are defined on the old page. You can also directly modify view transition names in the listener as snapshots for the ::view-transition-old
pseudo elements are taken after this event is dispatched.
Similarly, pagereveal
listeners allow you to tweak view transition names and types for the new page. Again, live images for the ::view-transition-new
pseudo-elements are being captured after the event.
The pageswap
event also provides a NavigationActivation
↗ object via its activation
property, offering details about the current page (from
) and next page (entry
). While the pagereveal
event does not include this property, in browsers supporting the Navigation API, you can use navigation.activation
to access information about the previous page (from
) and current page (entry
).