Last updated: First published:
View Transition API Details
The reference section lists several good sources of information. The most detailed, accurate and exhausting to read are the drafts of the specs.
Two Leveled Spec
The View Transition API comes in two levels: Level 1 specifies same-document transitions and level 2 defines cross-document transitions.
Level 1 / Same Document Transitions
The View Transition API (Level 1) consists of a single function and a single CSS property: These are the types that are introduced by the View Transition API:
In CSS you can use the following pseudo elements to address the animations.
All but ::view-transition
are parametrized with a view transition name (or *
for all).
Level 2 / Cross Document Transitions
The level 2 spec adds a new property and extends the signature of the startViewTransition()
function
On the CSS side, the pseudo-elements are unchanged, but now you can also use view transition classes to select animations in addition to *
and view transition names.
Level 2 also defines two CSS pseudo-classes on the document that can be used to select based on the existence and type of the active view transition
Also new in level 2 is the ability to make one transition group a child of another transition group setting the view-transition-group
property:
And level 2 defines a new at-rule that can be used to specify whether a page should participate in automatic cross-document view transitions and in what form.
View from the Outside
The startViewTransition()
function returns immediately yielding an object with three promises, updateCallbackDone
, ready
, and finished
, and a function called skipTransition()
. The returned promises can be used to trigger actions that depend on the progress of the view transition process. The skipTransition()
function lets you skip the visual animations.
In case of a cross-document transition, startViewTransition()
is implicitly called by the browser with an update callback that swaps in the DOM for the new page. The types property of the option
Object is initialized with the types of the @view-transition
at-rule, if specified. The resulting ViewTransition object can be accessed via the viewTransition
property of the new pageswap
and pagereveal
events.
Here is a high-level description of what is going on and how that can be observed from the out side using the promises:
Pending-capture: First, startViewTransition()
saves old-images of all HTML elements that have a view-transition-name
CSS property. By default this is only the <html>
root element of your document, which is automatically named root
.
The function then runs the update callback to transform the current document into a new version.
Update-callback-called: You can react to the end of the update with
the updateCallbackDone
promise. The update callback even runs when errors occurred
in the capture phase. The basic idea here is that the update of the document is a
mandatory part of view transition, while the visual animation is a nice add-on.
After that, new-images are drawn above the old images and a tree of ::view-transition
pseudo elements is created. When all is set up, the ready
promise fulfills.
Animating: Insertion of the pseudo elements into the DOM triggers animations that fade out the old-images and fade in the new-images. For each pair of elements with the same view transition name in the old and in the new version of the DOM, there is an additional animation that moves the image pair from the old position and size to the new position and size.1 All animations happen at the same time in parallel.
Done: When all those animations completed, the finished
promise
fulfills.
If you call viewTransition.skipTransition()
this aborts the animation part, but not the document update.
Detailed Interior View
This is a more elaborated description of what the startViewTransitions()
function does. I tried to keep the abstraction somewhere between the high-level view and the very detailed view given in the spec.
-
In the capture phase, the browser saves a bitmap of the current state for each HTML element that has a
view-transition-name
CSS property. This can later be addressed using the::view-transition-old(<name>)
pseudo element. Together with the image data, further CSS properties are recorded1.
For the captures, elements are ordered from bottom to top starting at the lowestz-index
. While capturing an image, other elements of “higher” images are not painted and thus the capture shows the element’s background in these areas.
Allview-transition-name
values used in the document must be unique. Otherwise the view transition is aborted (after calling the update callback, see next step). -
If an update callback is provided, the browser updates the document by starting and awaiting that callback. The
viewTransition.updateCallbackDone
promise reflects the execution of the callback. If no callback was provided, theupdateCallbackDone
promise fulfils immediately.
Even though update callback can be an asynchronous function, browsers typically time-out after a few seconds if the promise does not settle in time. Chrome for example throws aTimeoutError
after 4 seconds. For this reason all time consuming preparations, like loading a new DOM over the network, should be executed before callingstartViewTransition()
.
The promise might resolve or reject. If it rejects, theready
and thefinished
promises also reject and the whole view transition is aborted.
Before the update callback runs, rendering is blocked in the browser untilready
settles. The view is frozen. Another good reason to hurry up during the execution of the update callback. -
View transition processing then generates a new stacking context called the view transition layer that sits above all HTML content on the new page.
The bitmaps from step 1 are placed in that new layer. Position and saved CSS properties, most notably size and transformations, are those of the original elements from which they were screen-shotted. -
The next step now identifies elements with view transition names in the now updated document and places images for those elements in the view transition layer that was generated in the previous step. If the elements have view transition names that already appeared on the previous page, they are positioned and transformed to exactly cover the corresponding images added in the previous step. Otherwise position, size and CSS properties are those of the elements from the new document. The new images can be addressed by the
::view-transition-new(<name>)
pseudo element.
It might be worth noting, that not the new elements are placed in the view transition layer but only their images as replaced elements. -
For each view transition name (other than
none
) found in the previous or updated state of the document, the browser generates a::view-transition-image-pair(<name>)
pseudo element that becomes the direct parent of::view-transition-old(<name>)
and::view-transition-new(<name>)
. As not necessarily both images might exist for a given view transition name, the::view-transition-image-pair(<name>)
has one or two children.
For each::view-transition-image-pair(<name>)
a::view-transition-group(<name>)
pseudo element is created as the direct parent. All::view-transition-group(<name>)
are children of a global::view-transition
pseudo element.
Groups that only have an old image form exit animations, groups that only have a new image form an entry animation. The CSS pseudo classes:only-child
/:not(:only-child)
can be used to detect that situation and alter animations accordingly.
By default, the browser uses a fade-out animation for each old image and a fade-in animation for each new image. For each::view-transition-group(<name>)
with two images, the browser dynamically generates an animation that transitions both images from the old position and saved CSS properties to the new position and current properties. These animations are sometimes called morph or group animations. -
Now the
::view-transition
pseudo element is inserted at the documents<html>
element, rendering is unblocked again, and theviewTransition.ready
promise fulfills. The occurrence of the::view-transition
pseudo element with all its children triggers the view transition animations, which now all run in parallel. The hierarchy of pseudo elements only exists during the animation phase of the view transition.
Theready
promise may be used to start custom JavaScript animations, e.g. using the Animation API, GSAP, Anime.js and the like.
Render order is back to front. For pairs with two children, the new image is drawn in front of the old image. Entry animations are drawn in front of the other animations.
With their corresponding new images in the view transition layer, elements with view transition names will be exempt from drawing when the current DOM is rendered, leaving holes there as if the elements were styled withvisibility: hidden
. During the animation, the new images will transition to the holes and close the gaps. -
When all view transition animations come to an end, the
::view-transition
pseudo element with all its children is removed again, theviewTransition.finished
promise fulfills, and the view transition ends.
Footnotes
-
The CSS properties saved together with the image data include, width, height, transform, backdrop-filter and others. For details see https://drafts.csswg.org/css-view-transitions/#style-transition-pseudo-elements-algorithm↗ ↩ ↩2