CSS View Transitions Module Level 1

W3C Working Draft,

More details about this document
This version:
https://www.w3.org/TR/2023/WD-css-view-transitions-1-20230127/
Latest published version:
https://www.w3.org/TR/css-view-transitions-1/
Editor's Draft:
https://drafts.csswg.org/css-view-transitions-1/
History:
https://www.w3.org/standards/history/css-view-transitions-1
Feedback:
CSSWG Issues Repository
Inline In Spec
Editors:
Tab Atkins-Bittner (Google)
Jake Archibald (Google)
Khushal Sagar (Google)
Suggest an Edit for this Spec:
GitHub Editor

Abstract

This module defines the View Transition API, along with associated properties and pseudo-elements.

CSS is a language for describing the rendering of structured documents (such as HTML and XML) on screen, on paper, etc.

Status of this document

This section describes the status of this document at the time of its publication. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at https://www.w3.org/TR/.

This document was published by the CSS Working Group as a Working Draft using the Recommendation track. Publication as a Working Draft does not imply endorsement by W3C and its Members.

This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.

Please send feedback by filing issues in GitHub (preferred), including the spec code “css-view-transitions” in the title, like this: “[css-view-transitions] …summary of comment…”. All issues and comments are archived. Alternately, feedback can be sent to the (archived) public mailing list www-style@w3.org.

This document is governed by the 2 November 2021 W3C Process Document.

This document was produced by a group operating under the W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.

1. Introduction

This section is non-normative.

View Transitions is a feature that allows DOM changes to smoothly animate between states.

1.1. Separating transitions from DOM updates

Traditionally, creating a transition between two states involves a period where both states are present in the DOM at the same time. In fact, it usually involves creating a specific DOM structure that can contain both states. If one element is "moving" between containers, that element often needs to exist outside of either container for the period of the transition, to avoid clipping from either container or ancestor elements.

This extra in-between state often results in UX and accessibility issues, as the structure of the DOM is compromised for a purely-visual effect.

View transitions avoid this troublesome in-between state by allowing the DOM to switch between states synchronously, then performing a customizable visual transition between the two states in another layer, using a static capture of the old state, and a live capture of the new state.

These captures are used in a tree of pseudo-elements (detailed in § 4.3 View transition pseudo-elements), where the old state cross-fades with the new state, while animating from the old to new size and position.

Since the captures are rendered in pseudo-elements, developers can customize the transition using familiar features, such as CSS and web animations.

The developer chooses which elements are captured independently, meaning they can be animated independently, using the view-transition-name CSS property.

1.2. Lifecycle

A successful view transition goes through the following phases:

  1. Developer calls document.startViewTransition(updateCallback), which returns viewTransition, a ViewTransition.

  2. Current state captured as the "old" state.

  3. Rendering paused.

  4. Developer’s updateCallback is called, where they update document state.

  5. viewTransition.updateCallbackDone fulfills.

  6. Current state captured as the "new" state.

  7. Transition pseudo-elements created. See § 4.3 View transition pseudo-elements for an overview of this structure.

  8. Rendering unpaused, revealing the transition pseudo-elements.

  9. viewTransition.ready fulfills.

  10. Pseudo-elements animate until finished.

  11. Transition pseudo-elements removed.

  12. viewTransition.finished fulfills.

1.3. Transitions as an enhancement

A key part of this API design is that an animated transition is an enhancement to a document state change.

That means, a failure to create a 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 animated transition cannot happen.

For example, if the developer calls skipTransition() at the start of the lifecycle, the steps relating to the animated transition, such as creating the pseudo-elements, will not happen. However, the UpdateCallback will still be called. It’s only the transition that’s skipped, not the whole state change.

If the DOM change should also be skipped, then that should be handled by another feature. navigateEvent.signal is an example of a feature developers could use to handle this.

Although the transition API allows DOM changes to be asynchronous via the UpdateCallback, the transition API is not responsible for queuing or otherwise scheduling the DOM changes beyond the 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.

1.4. Examples

Taking a page that already updates its content using a pattern like this:
function spaNavigate(data) {
	updateTheDOMSomehow(data);
}

A transition could be added like this:

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 tree of pseudo-elements, so customizations can be made using CSS. For example:

::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:
@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:
.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:

.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:

::view-transition-image-pair(root) {
	isolation: auto;
}

::view-transition-old(root),
::view-transition-new(root) {
	animation: none;
	mix-blend-mode: normal;
}

Then, the JavaScript:

// 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:

2. CSS properties

2.1. view-transition-name

Name: view-transition-name
Value: none | <custom-ident>
Initial: none
Applies to: all elements
Inherited: no
Percentages: n/a
Computed value: as specified
Canonical order: per grammar
Animation type: discrete

The view-transition-name property "names" an element as participating in a view transition.

none

The element will not participate in a view transition.

<custom-ident>

The element can participate in a view transition, as either an old or new element, with a view transition name equal to the <custom-ident>'s value.

Note: The value none is invalid as a <custom-ident>.

Note: This property causes the user-agent to both capture separate snapshots from the elements, as well as create separate pseudo-element sub-trees representing this element’s "old" and "new" states. For the purposes of this API, if one element has a transition-name "foo" in the old state, and another element has a transition-name "foo" in the new state, they are treated as representing different visual state of the same element. 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, that we happen to call "element".

3. User-agent styles

The following is added to the HTML user agent style sheet:

@keyframes -ua-view-transition-fade-out {
	to { opacity: 0; }
}

@keyframes -ua-view-transition-fade-in {
	from { opacity: 0; }
}

html {
	view-transition-name: root;
}
There are further additions to the HTML user agent style sheet defined in § 4.3 View transition pseudo-elements for each pseudo-element, and other styles are added at runtime via the document's view transition style sheet.

4. Pseudo-elements

4.1. Pseudo-element 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 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 descendant tree-abiding pseudo-elements.

When a pseudo-element participates in a pseudo-element tree, its originating pseudo-element is its parent.

If a descendant pseudo of a pseudo-element root has no other 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-transitions-image-pair(ident) contains a single child. As in, there is no sibling ::view-transition-old(ident).

4.2. Named view-transition pseudo-elements

A named view-transition pseudo-element is a type of tree-abiding pseudo-elements.

It has a view-transition name, a string.

Their selector takes a <pt-name-selector> argument.

<pt-name-selector> = '*' | <custom-ident>

The selector matches if the <pt-name-selector> is * or matches the named view-transition pseudo-element's view-transition name.

The specificity of a view-transition selector with a <custom-ident> argument is the same as for other pseudo-elements, and is equivalent to a type selector.

The specificity of a view-transition selector with a * argument is zero.

Note: The view-transition name is set to the view-transition-name that triggered its creation.

4.3. View transition pseudo-elements

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 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.

::view-transition
This element provides a containing block for all ::view-transition-group() pseudo-elements.

A tree-abiding pseudo-element that is also a pseudo-element root. Its originating element is the document’s document element.

Its containing block is the snapshot root.

The following is added to the HTML user agent style sheet:

html::view-transition {
	position: fixed;
	inset: 0;
}
The aim of the style is to size the pseudo-element to cover the snapshot root and position all ::view-transition-group() pseudo-elements relative to the snapshot root origin.
::view-transition-group( <pt-name-selector> )
This element initially mirrors the size and position of the "old" element, or the "new" element if there isn’t an "old" element. It is always a child of a ::view-transition.

If there’s both an "old" and "new" state, styles in the view transition style sheet animate this pseudo-element’s width and height from the size of the old element’s border box to that of the new element’s border box.

Also the element’s transform is animated from the old element’s screen space transform to the new element’s screen space transform.

This style is generated dynamically since the values of animated properties are determined at the time that the transition begins.

A tree-abiding pseudo-element that is also a named view-transition pseudo-element, and participates in a pseudo-element tree.

It is selected from its ultimate originating element, the document element.

The following is added to the HTML user agent style sheet:

html::view-transition-group(*) {
	position: absolute;
	top: 0;
	left: 0;

	animation-duration: 0.25s;
	animation-fill-mode: both;
}
::view-transition-image-pair( <pt-name-selector> )
This element is a child of the group element and provides isolation: isolate for its children. It’s needed so that its children can be blended with non-normal blend modes without affecting other visual outputs.

It is always present as a child of each ::view-transition-group().

A tree-abiding pseudo-element that is also a named view-transition pseudo-element, and participates in a pseudo-element tree.

It is selected from its ultimate originating element, the document element.

The following is added to the HTML user agent style sheet:

html::view-transition-image-pair(*) {
	position: absolute;
	inset: 0;

	animation-duration: inherit;
	animation-fill-mode: inherit;
}
::view-transition-old( <pt-name-selector> )

This element is a replaced element that produced the visual representation of the "old" state taken from user-agent provided snapshots.

This is only ever a child of a ::view-transition-image-pair(), never has any children, and is omitted if there’s no "old" state to represent.

:only-child can be used to match cases where this element is the only element in the ::view-transition-image-pair().

The appearance of this element can be manipulated with object-* properties in the same way that other replaced elements can be.

A tree-abiding pseudo-element that is also a named view-transition pseudo-element, and participates in a pseudo-element tree.

It is selected from its ultimate originating element, the document element.

It is a replaced element, with natural dimensions equal to the content’s size.

Note: The image content is captured in capture the image, then set and updated in setup transition pseudo-elements and update pseudo-element styles.

The following is added to the HTML user agent style sheet:

html::view-transition-old(*) {
	position: absolute;
	inset-block-start: 0;
	inline-size: 100%;
	block-size: auto;

	animation-name: -ua-view-transition-fade-out;
	animation-duration: inherit;
	animation-fill-mode: inherit;
}

Note: The aim of the style is to match the element’s inline size while retaining the aspect ratio. It is also placed at the block start.

Note: Additional styles in the view transition style sheet added to animate these pseudo-elements are detailed in setup transition pseudo-elements and update pseudo-element styles.

::view-transition-new( <pt-name-selector> )

Identical to ::view-transition-old(), except the following styles added to the HTML user agent style sheet:

html::view-transition-old(*) {
	position: absolute;
	inset-block-start: 0;
	inline-size: 100%;
	block-size: auto;

	animation-name: -ua-view-transition-fade-in;
	animation-duration: inherit;
	animation-fill-mode: inherit;
}

Note: The construction of this tree is performed in the setup transition pseudo-elements algorithm.

5. Concepts

5.1. Phases

Phases represent an ordered sequence of states. Since phases are ordered, prose can refer to phases before a particular phase, meaning they appear earlier in the sequence.

The initial phase is the first item in the sequence.

5.2. The snapshot root

The snapshot root is a rectangle that covers all areas of the window that could potentially display web content. This area is consistent regardless of root scrollbars or interactive widgets.

"Interactive widgets" refers to UI described within a property definition in CSS Viewport 1 § 3.4 interactive-widget. This should be extracted into an exported definition that can be used independently of the property.

A diagram of a phone screen, including a top status bar, a browser URL bar, web-content area with a floating scrollbar, a virtual keyboard, and a bottom bar with an OS back button The previous diagram, but highlights the area that’s the 'snapshot root', which includes everything except the top status bar and the bottom bar with the OS back button
An example of the snapshot root on a mobile OS. The snapshot includes the URL bar, as this can be scrolled away. The keyboard is included as this appears and disappears. The top and bottom bars are part of the OS rather than the browser, so they’re not included in the snapshot root.
A diagram of a desktop browser window, including a tab bar, a URL bar, and a web-content area featuring both horizontal and vertical scrollbars The previous diagram, but highlights the area that’s the 'snapshot root', which includes the web content area and the scrollbars
An example of the snapshot root on a desktop OS. This includes the scrollbars, but does not include the URL bar, as web content never appears in that area.

This means the snapshot canvas size is likely to be consistent for the document element's old image and new element.

The snapshot root origin refers to the top-left corner of the snapshot root.

The snapshot root size refers to the width and height of the snapshot root as a tuple of two numbers.

5.3. The view-transition layer stacking layer

This specification introduces a stacking layer to the Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification § elaborate-stacking-contexts.

The ::view-transition pseudo-element generates a new stacking context called the view-transition layer with the following characteristics:

Note: The intent of the feature is to be able to capture the contents of the page, which includes the top layer elements. In order to accomplish that, the view-transition layer cannot be a part of the captured top layer context, since that results in a circular dependency. Instead, this stacking context is a sibling of other page contents.

Do we need to clarify that the stacking context for the root and top layer elements has filters and effects coming from the document element's style?

5.4. Captured elements

A captured element is a struct with the following:

old image

an image or null. Initially null.

The type of "image" needs to be linked or defined.

old styles

a set of styles or null. Initially null.

The type of "a set of styles" needs to be linked or defined.

new element

an element or null. Initially null.

The type of "element" needs to be linked or defined.

In addition, a captured element has the following style definitions:

group keyframes

A CSSKeyframesRule or null. Initially null.

group animation name rule

A CSSStyleRule or null. Initially null.

group styles rule

A CSSStyleRule or null. Initially null.

old view-box rule

A CSSStyleRule or null. Initially null.

new view-box rule

A CSSStyleRule or null. Initially null.

image pair isolation rule

A CSSStyleRule or null. Initially null.

view blend mode rule

A CSSStyleRule or null. Initially null.

Note: These are used to update, and later remove styles from a document's view transition style sheet.

5.5. Additions to Document

A Document additionally has:

active DOM transition

a ViewTransition or null. Initially null.

transition suppressing rendering

a boolean. Initially false.

view transition style sheet

a style sheet. Initially a new style sheet in the user-agent origin, ordered after the HTML user agent style sheet.

Note: This is used to hold dynamic styles relating to transitions.

show view-transition root pseudo-element

A boolean. Initially false.

When this is true, this's active DOM transition's transition root pseudo-element renders as a child of this's document element, and this's document element is its originating element.

Note: The position of the transition root pseudo-element within the document element does not matter, as the transition root pseudo-element's containing block is the snapshot root.

6. API

6.1. Additions to Document

partial interface Document {
  ViewTransition startViewTransition(optional UpdateCallback? updateCallback = null);
};

callback UpdateCallback = Promise<any> ();
viewTransition = document.startViewTransition(updateCallback)

Starts a new view transition.

updateCallback is called asynchronously, once the current state of the document is captured. Then, when the promise returned by updateCallback fulfills, the new state of the document is captured.

updateCallback is _always_ called, even if the transition cannot happen (e.g. due to duplicate view-transition-name values). The transition is an enhancement around the state change, so a failure to create a transition never prevents the state change. See § 1.3 Transitions as an enhancement for more details on this principle.

If the promise returned by updateCallback rejects, the transition is skipped.

6.1.1. startViewTransition()

The method steps for startViewTransition(updateCallback) are as follows:
  1. Let transition be a new ViewTransition object in this’s relevant Realm.

  2. Set transition’s update callback to updateCallback.

  3. Let document be this’s relevant global object’s associated document.

  4. If document’s active DOM transition is not null, then skip the view transition document’s active DOM transition with an "AbortError" DOMException in this’s relevant Realm.

    Note: This can result in two asynchronous update callbacks running concurrently. One for the document’s current active DOM transition, and another for this transition. As per the design of this feature, it’s assumed that the developer is using another feature or framework to correctly schedule these DOM changes.

  5. Set document’s active DOM transition to transition.

    Note: The process continues in setup view transition, via perform pending transition operations, which is called in § 7.1 Monkey patches to rendering.

  6. Return transition.

6.2. The ViewTransition interface

[Exposed=Window]
interface ViewTransition {
  readonly attribute Promise<undefined> updateCallbackDone;
  readonly attribute Promise<undefined> ready;
  readonly attribute Promise<undefined> finished;
  undefined skipTransition();
};

Note: The ViewTransition represents and controls a single same-document transition. That is, it controls a transition where the starting and ending document are the same, possibly with changes to the document’s DOM structure.

viewTransition.updateCallbackDone

A promise that fulfills when the promise returned by updateCallback fulfills, or rejects when it rejects.

The View Transition API wraps a DOM change and creates a transition. However, sometimes you don’t care about the success/failure of the transition animation, you just want to know if and when the DOM change happens. updateCallbackDone is for that use-case.

viewTransition.ready

A promise that fulfills once the pseudo-elements for the transition are created, and the animation is about to start.

It rejects if the transition cannot begin. This can be due to misconfiguration, such as duplicate 'view-transition-name’s, or if updateCallbackDone returns a rejected promise.

The point that ready fulfills is the ideal opportunity to animate the view transition pseudo-elements with the web animation API.

viewTransition.finished

A promise that fulfills once the end state is fully visible and interactive to the user.

It only rejects if updateCallback returns a rejected promise, as this indicates the end state wasn’t created.

Otherwise, if a transition fails to begin, or is skipped (from skipTransition()), the end state is still reached, so finished fulfills.

viewTransition.skipTransition()

Immediately finish the transition, or prevent it starting.

This never prevents updateCallback being called, as the DOM change is separate to the transition. See § 1.3 Transitions as an enhancement for more details on this principle.

If this is called before ready resolves, ready will reject.

If finished hasn’t resolved, it will fulfill or reject along with updateCallbackDone.

A ViewTransition has the following:

named elements

a map, whose keys are view transition names and whose values are captured elements. Initially a new map.

phase

One of the following phases:

  1. "pending-capture".

  2. "update-callback-called".

  3. "animating".

  4. "done".

Note: For the most part, a developer using this API does not need to worry about the different phases, since they progress automatically. It is, however, important to understand what steps happen in each of the phases: when the snapshots are captured, when pseudo-element DOM is created, etc. The description of the phases below tries to be as precise as possible, with an intent to provide an unambiguous set of steps for implementors to follow in order to produce a spec-compliant implementation.

update callback

an UpdateCallback or null. Initially null.

ready promise

a Promise. Initially a new promise in this’s relevant Realm.

update callback done promise

a Promise. Initially a new promise in this’s relevant Realm.

finished promise

a Promise. Initially a new promise in this’s relevant Realm.

transition root pseudo-element

a ::view-transition. Initially a new ::view-transition.

initial snapshot root size

a tuple of two numbers (width and height), or null. Initially null.

Note: This is used to detect changes in the snapshot root size, which causes the transition to skip. Discussion of this behavior.

The finished getter steps are to return this’s finished promise.

The ready getter steps are to return this’s ready promise.

The updateCallbackDone getter steps are to return this’s update callback done promise.

6.2.1. skipTransition()

The method steps for skipTransition() are:
  1. If this's phase is not "done", then skip the view transition for this with an "AbortError" DOMException.

7. Algorithms

7.1. Monkey patches to rendering

Run the following steps before intersection observer steps in the update the rendering steps:
  1. For each fully active Document in docs, perform pending transition operations for that Document.

Note: These steps will be added to the update the rendering in the HTML spec. As such, the prose style is written to match other steps in that algorithm.

In the definition for rendering opportunity, add the following condition:

A navigable has no rendering opportunities if active document has transition suppressing rendering set to true.

Note: These steps will be added to the update the rendering in the HTML spec. See #7884 for more context.

7.2. Perform pending transition operations

To perform pending transition operations given a Document document, perform the following steps:
  1. If document’s active DOM transition is not null, then:

    1. If document’s active DOM transition's phase is "pending-capture", then setup view transition with document’s active DOM transition.

    2. Otherwise, if document’s active DOM transition's phase is "animating", then update transition DOM for document’s active DOM transition.

7.3. Setup view transition

To setup view transition given a ViewTransition transition, perform the following steps:
  1. Let namedElements be transition’s named elements.

  2. Let usedTransitionNames be a new set of strings.

  3. Let document be transition’s relevant global object’s associated document.

  4. Set transition’s initial snapshot root size to the snapshot root size.

  5. For each element of every Element and pseudo-element connected to document, in paint order:

    The link for "paint order" doesn’t seem right. Is there a more canonical definition?

    1. If any flat tree ancestor of this element skips its contents, then continue.

    2. Let transitionName be the computed value of view-transition-name for element.

    3. If transitionName is none, or element is not rendered, then continue.

    4. If any of the following is true:

      Then skip the view transition for transition with an "InvalidStateError" DOMException in transition’s relevant Realm, and return.

    5. Append transitionName to usedTransitionNames.

    6. Let capture be a new captured element struct.

    7. Set capture’s old image to the result of capturing the image of element.

    8. Set capture’s old styles to the following:

      transform

      A transform that would map element’s border box from the snapshot root origin to its current visual position.

      This value is identity for the document element.

      width
      height

      The size of the snapshot root if element is the document element, otherwise, the width and height of element’s border box.

      object-view-box

      none if element is the document element, otherwise, an object-view-box value that, when applied to the old image, will cause the view box to coincide with element’s border box in the image. This must be expressed using inset().

      writing-mode

      The writing-mode of element.

      direction

      The direction of element.

      This needs proper types.

    9. Set namedElements[transitionName] to capture.

  6. Set document’s transition suppressing rendering to true.

  7. Queue a global task on the DOM manipulation task source, given transition’s relevant global object, to execute the following steps:

    Note: A task is queued here because the texture read back in capturing the image may be async, although the render steps in the HTML spec act as if it’s synchronous.

    "DOM manipulation task source" doesn’t link due to a bikeshed bug.

    1. If transition’s phase is "done", then abort these steps.

      Note: This happens if transition was skipped before this point.

    2. Call the update callback of transition.

    3. React to transition’s update callback done promise:

7.4. Skip the view transition

To skip the view transition for ViewTransition transition with reason reason:
  1. Let document be transition’s relevant global object’s associated document.

  2. Assert: document’s active DOM transition is transition.

  3. Assert: transition’s phase is not "done".

  4. If transition’s phase is before "update-callback-called", then call the update callback of transition.

  5. Set transition suppressing rendering to false.

  6. Clear view transition transition.

  7. Set transition’s phase to "done".

  8. If transition’s ready promise has not yet been resolved, reject it with reason.

    Note: The ready promise would’ve been resolved if skipTransition() is called after we start animating.

  9. React to transition’s update callback done promise:

7.5. Capture the image

To capture the image given an Element element, perform the following steps. They return an image.
  1. If element is the document element, then:

    1. Render the region of the element and the top layer that intersects the snapshot root, on a transparent canvas the size of the snapshot root, following the capture rendering characteristics, and these additional characteristics:

      • Areas outside element’s scrolling box should be rendered as if they were scrolled to, without moving or resizing the layout viewport. This must not trigger events related to scrolling or resizing, such as IntersectionObservers.

        A phone browser window, showing a URL bar, a fixed-position element directly beneath it, and some page content beneath that. A scroll bar indicates the page has been scrolled significantly. The captured snapshot. It shows that content beneath the URL bar was included in the capture.
        An example of what the user sees compared to the captured snapshot. This example assumes the root is the only element with a transition name.
      • Areas that cannot be scrolled to (i.e. they are out of scrolling bounds), should render the canvas background.

        A phone browser window, showing a URL bar, and some content beneath. A scroll bar indicates the page is scrolled to the top. The captured snapshot. It shows the area underneath the URL bar as the same color as the rest of the document.
        An example of what the user sees compared to the captured snapshot. This example assumes the root is the only element with a transition name.
    2. Return the canvas as an image. The natural size of the image is equal to the snapshot root.

  2. Otherwise:

    1. Render element and its descendants, at the same size it appears in its node document, over an infinite transparent canvas, following the capture rendering characteristics.

    2. Let interestRectangle be the result of computing the interest rectangle for element.

      Note: The interestRectangle is the subset of element’s ink overflow rectangle that should be captured. This is required for cases where an element’s ink overflow rectangle needs to be clipped because of hardware constraints. For example, if it exceeds the maximum texture size.

    3. Return the portion of the canvas within interestRectangle as an image. The natural size of the image is equal to the interestRectangle bounds.

The capture rendering characteristics are as follows:

7.6. Update transition DOM

To update transition DOM given a ViewTransition transition:
  1. Let document be transition’s relevant global object’s associated document.

  2. Let hasActiveAnimations be a boolean, initially false.

  3. For each element of transition’s transition root pseudo-element's inclusive descendants:

    1. For each animation whose timeline is a document timeline associated with document, and contains at least one associated effect whose effect target is element, set hasActiveAnimations to true if any of the following conditions is true:

      The prose around "effect target" is incorrect, but #8001 needs to land before it can be fixed.

  4. If hasActiveAnimations is false:

    1. Set transition’s phase to "done".

    2. Clear view transition transition.

    3. Resolve transition’s finished promise.

    4. Return.

  5. If transition’s initial snapshot root size is not equal to the snapshot root size, then skip the view transition for transition, and return.

  6. Update pseudo-element styles for transition.

    Note: The above implies that a change in incoming element’s size or position will cause a new keyframe to be generated. This can cause a visual jump. We could retarget smoothly but don’t have a use-case to justify the complexity. See issue 7813 for details.

7.7. Compute the interest rectangle

To compute the interest rectangle of an Element element, perform the following steps. They return a rectangle.
  1. Assert: element is not element’s node document's document element.

    Note: The document element is captured differently, as specified in capture the image.

  2. If element’s ink overflow area does not exceed an implementation-defined maximum size, then return a rectangle that is equal to element’s ink overflow rectangle.

  3. Otherwise:

    Define the algorithm used to clip the snapshot when it exceeds max size.

7.8. Setup transition pseudo-elements

To setup transition pseudo-elements for a ViewTransition transition:
  1. Let document be this’s relevant global object’s associated document.

  2. Set document’s show view-transition root pseudo-element to true.

  3. For each transitionNamecapturedElement of transition’s named elements:

    1. Let group be a new ::view-transition-group(), with its view-transition name set to transitionName.

    2. Append group to transition’s transition root pseudo-element.

    3. Let imagePair be a new ::view-transition-image-pair(), with its view-transition name set to transitionName.

    4. Append imagePair to group.

    5. If capturedElement’s old image is not null, then:

      1. Let old be a new ::view-transition-old(), with its view-transition name set to transitionName, displaying capturedElement’s old image.

      2. Append old to imagePair.

      3. Let oldViewBox be capturedElement’s old styles object-view-box property.

      4. Set capturedElement’s old view-box rule to a new CSSStyleRule representing the following CSS, and append it to document’s view transition style sheet.

        html::view-transition-old(transitionName) {
          object-view-box: oldViewBox;
        }
        

        Note: The above code example contains variables to be replaced.

    6. If capturedElement’s new element is not null, then:

      1. Let new be a new ::view-transition-new(), with its view-transition name set to transitionName, displaying the capture the image of capturedElement’s new element.

      2. Append new to imagePair.

      The new element and its contents (the flat tree descendants of the element, including both text and elements, or the replaced content of a replaced element), except the transition’s transition root pseudo-element's inclusive descendants, are not painted (as if they had visibility: hidden) and do not respond to hit-testing (as if they had pointer-events: none) until new exists.

    7. If both of capturedElement’s old image and new element are not null, then:

      1. Let transform be capturedElement’s old styles's transform property.

      2. Let width be capturedElement’s old styles's width property.

      3. Let height be capturedElement’s old styles's height property.

      4. Set capturedElement’s group keyframes to a new CSSKeyframesRule representing the following CSS, and append it to document’s view transition style sheet:

        @keyframes -ua-view-transition-group-anim-transitionName {
          from {
            transform: transform;
            width: width;
            height: height;
          }
        }
        

        Note: The above code example contains variables to be replaced.

      5. Set capturedElement’s group animation name rule to a new CSSStyleRule representing the following CSS, and append it to document’s view transition style sheet:

        html::view-transition-group(transitionName) {
          animation-name: -ua-view-transition-group-anim-transitionName;
        }
        

        Note: The above code example contains variables to be replaced.

      6. Set capturedElement’s image pair isolation rule to a new CSSStyleRule representing the following CSS, and append it to document’s view transition style sheet:

        html::view-transition-image-pair(transitionName) {
          isolation: isolate;
        }
        

        Note: The above code example contains variables to be replaced.

      7. Set capturedElement’s view blend mode rule to a new CSSStyleRule representing the following CSS, and append it to document’s view transition style sheet:

        html::view-transition-old(transitionName),
        html::view-transition-new(transitionName) {
          mix-blend-mode: plus-lighter;
        }
        

        Note: The above code example contains variables to be replaced.

        Note: mix-blend-mode: plus-lighter ensures that the blending of identical pixels from the old and new images results in the same color value as those pixels, and achieves a "correct" cross-fade.

        Isolation and the dynamic setting of blending is only necessary to get the right cross-fade between new and old image pixels. Would it be simpler to always add it and try to optimize in the implementation?

7.9. Update pseudo-element styles

To update pseudo-element styles for a ViewTransition transition:
  1. For each transitionNamecapturedElement of transition’s named elements:

    1. Let width, height, transform, writingMode, and direction be null.

    2. If capturedElement’s new element is null, then:

      1. Set width to capturedElement’s old styles width property.

      2. Set height to capturedElement’s old styles height property.

      3. Set transform to capturedElement’s old styles transform property.

      4. Set writingMode to capturedElement’s old styles writing-mode property.

      5. Set direction to capturedElement’s old styles direction property.

    3. Otherwise:

      1. Set width to the current width of capturedElement’s new element's border box.

      2. Set height to the current height of capturedElement’s new element's border box.

      3. Set transform to a transform that would map capturedElement’s new element's border box from the snapshot root origin to its current visual position.

      4. Set writingMode to the computed value of writing-mode on capturedElement’s new element.

      5. Set direction to the computed value of direction on capturedElement’s new element.

    4. If capturedElement’s group styles rule is null, then set capturedElement’s group styles rule to a new CSSStyleRule representing the following CSS, and append it to document’s view transition style sheet.

      Otherwise, update capturedElement’s group styles rule to match the following CSS:

      html::view-transition-group(transitionName) {
        width: width;
        height: height;
        transform: transform;
        writing-mode: writingMode;
        direction: direction;
      }
      

      Note: The above code example contains variables to be replaced.

    5. If capturedElement’s new element is not null, then:

      1. Let new be the ::view-transition-new() replaced element pseudo-element, with the name transitionName, displaying the capture the image of capturedElement’s new element.

      2. Let newViewBox be an object-view-box value that when applied to new, will cause the view box to coincide with capturedElement’s new element's border box in the image. This must be expressed using inset().

      3. If capturedElement’s new view-box rule is null, then set capturedElement’s new view-box rule to a new CSSStyleRule representing the following CSS, and append it to document’s view transition style sheet.

        Otherwise, update capturedElement’s new view-box rule to match the following CSS:

        html::view-transition-new(transitionName) {
          object-view-box: newViewBox;
        }
        

        Note: The above code example contains variables to be replaced.

This algorithm must be executed to update styles in user-agent origin if its effects can be observed by a web API.

Note: An example of such a web API is window.getComputedStyle(document.documentElement, "::view-transition").

To call the update callback of a ViewTransition transition:
  1. Assert: transition’s phase is before "update-callback-called".

  2. Let callbackPromise be a new promise in transition’s relevant Realm.

  3. Set transition’s phase to "update-callback-called".

  4. React to callbackPromise:

7.10. Clear view transition

To clear view transition of a ViewTransition transition:
  1. Let document be transition’s relevant global object’s associated document.

  2. Assert: document’s active DOM transition is transition.

  3. For each capturedElement of transition’s named elements' values:

    1. For each style of capturedElement’s style definitions:

      1. If style is not null, and style is in document’s view transition style sheet, then remove style from document’s view transition style sheet.

  4. Set document’s show view-transition root pseudo-element to false.

  5. Set document’s active DOM transition to null.

Privacy Considerations

This specification introduces no new privacy considerations.

Security Considerations

The images generated using capture the image algorithm could contain cross-origin data (if the Document is embedding cross-origin resources) or sensitive information like visited links. The implementations must ensure this data can not be accessed by the Document. This should be feasible since access to this data should already be prevented in the default rendering of the Document.

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Advisements are normative sections styled to evoke special attention and are set apart from other normative text with <strong class="advisement">, like this: UAs MUST provide an accessible alternative.

Conformance classes

Conformance to this specification is defined for three conformance classes:

style sheet
A CSS style sheet.
renderer
A UA that interprets the semantics of a style sheet and renders documents that use them.
authoring tool
A UA that writes a style sheet.

A style sheet is conformant to this specification if all of its statements that use syntax defined in this module are valid according to the generic CSS grammar and the individual grammars of each feature defined in this module.

A renderer is conformant to this specification if, in addition to interpreting the style sheet as defined by the appropriate specifications, it supports all the features defined by this specification by parsing them correctly and rendering the document accordingly. However, the inability of a UA to correctly render a document due to limitations of the device does not make the UA non-conformant. (For example, a UA is not required to render color on a monochrome monitor.)

An authoring tool is conformant to this specification if it writes style sheets that are syntactically correct according to the generic CSS grammar and the individual grammars of each feature in this module, and meet all other conformance requirements of style sheets as described in this module.

Partial implementations

So that authors can exploit the forward-compatible parsing rules to assign fallback values, CSS renderers must treat as invalid (and ignore as appropriate) any at-rules, properties, property values, keywords, and other syntactic constructs for which they have no usable level of support. In particular, user agents must not selectively ignore unsupported component values and honor supported values in a single multi-value property declaration: if any value is considered invalid (as unsupported values must be), CSS requires that the entire declaration be ignored.

Implementations of Unstable and Proprietary Features

To avoid clashes with future stable CSS features, the CSSWG recommends following best practices for the implementation of unstable features and proprietary extensions to CSS.

Non-experimental implementations

Once a specification reaches the Candidate Recommendation stage, non-experimental implementations are possible, and implementors should release an unprefixed implementation of any CR-level feature they can demonstrate to be correctly implemented according to spec.

To establish and maintain the interoperability of CSS across implementations, the CSS Working Group requests that non-experimental CSS renderers submit an implementation report (and, if necessary, the testcases used for that implementation report) to the W3C before releasing an unprefixed implementation of any CSS features. Testcases submitted to W3C are subject to review and correction by the CSS Working Group.

Further information on submitting testcases and implementation reports can be found from on the CSS Working Group’s website at https://www.w3.org/Style/CSS/Test/. Questions should be directed to the public-css-testsuite@w3.org mailing list.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[COMPOSITING-1]
Rik Cabanier; Nikos Andronikos. Compositing and Blending Level 1. 13 January 2015. CR. URL: https://www.w3.org/TR/compositing-1/
[CSS-2022]
Tab Atkins Jr.; Elika Etemad; Florian Rivoal. CSS Snapshot 2022. 22 November 2022. NOTE. URL: https://www.w3.org/TR/css-2022/
[CSS-ANIMATIONS-1]
Dean Jackson; et al. CSS Animations Level 1. 11 October 2018. WD. URL: https://www.w3.org/TR/css-animations-1/
[CSS-BACKGROUNDS-3]
Bert Bos; Elika Etemad; Brad Kemper. CSS Backgrounds and Borders Module Level 3. 26 July 2021. CR. URL: https://www.w3.org/TR/css-backgrounds-3/
[CSS-BOX-4]
Elika Etemad. CSS Box Model Module Level 4. 3 November 2022. WD. URL: https://www.w3.org/TR/css-box-4/
[CSS-BREAK-4]
Rossen Atanassov; Elika Etemad. CSS Fragmentation Module Level 4. 18 December 2018. WD. URL: https://www.w3.org/TR/css-break-4/
[CSS-CASCADE-5]
Elika Etemad; Miriam Suzanne; Tab Atkins Jr.. CSS Cascading and Inheritance Level 5. 13 January 2022. CR. URL: https://www.w3.org/TR/css-cascade-5/
[CSS-COLOR-4]
Tab Atkins Jr.; Chris Lilley; Lea Verou. CSS Color Module Level 4. 1 November 2022. CR. URL: https://www.w3.org/TR/css-color-4/
[CSS-CONTAIN-2]
Tab Atkins Jr.; Florian Rivoal; Vladimir Levin. CSS Containment Module Level 2. 17 September 2022. WD. URL: https://www.w3.org/TR/css-contain-2/
[CSS-DISPLAY-3]
Tab Atkins Jr.; Elika Etemad. CSS Display Module Level 3. 18 November 2022. CR. URL: https://www.w3.org/TR/css-display-3/
[CSS-IMAGES-3]
Tab Atkins Jr.; Elika Etemad; Lea Verou. CSS Images Module Level 3. 17 December 2020. CR. URL: https://www.w3.org/TR/css-images-3/
[CSS-IMAGES-4]
Tab Atkins Jr.; Elika Etemad; Lea Verou. CSS Image Values and Replaced Content Module Level 4. 13 April 2017. WD. URL: https://www.w3.org/TR/css-images-4/
[CSS-IMAGES-5]
CSS Images Module Level 5 URL: https://drafts.csswg.org/css-images-5/
[CSS-OVERFLOW-3]
Elika Etemad; Florian Rivoal. CSS Overflow Module Level 3. 31 December 2022. WD. URL: https://www.w3.org/TR/css-overflow-3/
[CSS-PSEUDO-4]
Daniel Glazman; Elika Etemad; Alan Stearns. CSS Pseudo-Elements Module Level 4. 30 December 2022. WD. URL: https://www.w3.org/TR/css-pseudo-4/
[CSS-SCOPING-1]
Tab Atkins Jr.; Elika Etemad. CSS Scoping Module Level 1. 3 April 2014. WD. URL: https://www.w3.org/TR/css-scoping-1/
[CSS-SHAPES-1]
Rossen Atanassov; Alan Stearns. CSS Shapes Module Level 1. 15 November 2022. CR. URL: https://www.w3.org/TR/css-shapes-1/
[CSS-SIZING-3]
Tab Atkins Jr.; Elika Etemad. CSS Box Sizing Module Level 3. 17 December 2021. WD. URL: https://www.w3.org/TR/css-sizing-3/
[CSS-TRANSFORMS-1]
Simon Fraser; et al. CSS Transforms Module Level 1. 14 February 2019. CR. URL: https://www.w3.org/TR/css-transforms-1/
[CSS-VALUES-4]
Tab Atkins Jr.; Elika Etemad. CSS Values and Units Module Level 4. 19 October 2022. WD. URL: https://www.w3.org/TR/css-values-4/
[CSS-VIEWPORT]
CSS Viewport Module Level 1 URL: https://drafts.csswg.org/css-viewport/
[CSS-WRITING-MODES-3]
Elika Etemad; Koji Ishii. CSS Writing Modes Level 3. 10 December 2019. REC. URL: https://www.w3.org/TR/css-writing-modes-3/
[CSS-WRITING-MODES-4]
Elika Etemad; Koji Ishii. CSS Writing Modes Level 4. 30 July 2019. CR. URL: https://www.w3.org/TR/css-writing-modes-4/
[CSS21]
Bert Bos; et al. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification. 7 June 2011. REC. URL: https://www.w3.org/TR/CSS21/
[CSSOM-1]
Daniel Glazman; Emilio Cobos Álvarez. CSS Object Model (CSSOM). 26 August 2021. WD. URL: https://www.w3.org/TR/cssom-1/
[CSSOM-VIEW-1]
Simon Pieters. CSSOM View Module. 17 March 2016. WD. URL: https://www.w3.org/TR/cssom-view-1/
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[FILTER-EFFECTS-1]
Dirk Schulze; Dean Jackson. Filter Effects Module Level 1. 18 December 2018. WD. URL: https://www.w3.org/TR/filter-effects-1/
[FULLSCREEN]
Philip Jägenstedt. Fullscreen API Standard. Living Standard. URL: https://fullscreen.spec.whatwg.org/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[INTERSECTION-OBSERVER]
Stefan Zager; Emilio Cobos Álvarez. Intersection Observer. 6 July 2022. WD. URL: https://www.w3.org/TR/intersection-observer/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[SELECTORS-4]
Elika Etemad; Tab Atkins Jr.. Selectors Level 4. 11 November 2022. WD. URL: https://www.w3.org/TR/selectors-4/
[WEB-ANIMATIONS-1]
Brian Birtles; et al. Web Animations. 8 September 2022. WD. URL: https://www.w3.org/TR/web-animations-1/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

Property Index

Name Value Initial Applies to Inh. %ages Anim­ation type Canonical order Com­puted value
view-transition-name none | <custom-ident> none all elements no n/a discrete per grammar as specified

IDL Index

partial interface Document {
  ViewTransition startViewTransition(optional UpdateCallback? updateCallback = null);
};

callback UpdateCallback = Promise<any> ();

[Exposed=Window]
interface ViewTransition {
  readonly attribute Promise<undefined> updateCallbackDone;
  readonly attribute Promise<undefined> ready;
  readonly attribute Promise<undefined> finished;
  undefined skipTransition();
};

Issues Index

"Interactive widgets" refers to UI described within a property definition in CSS Viewport 1 § 3.4 interactive-widget. This should be extracted into an exported definition that can be used independently of the property.
Do we need to clarify that the stacking context for the root and top layer elements has filters and effects coming from the document element's style?
The type of "image" needs to be linked or defined.
The type of "a set of styles" needs to be linked or defined.
The type of "element" needs to be linked or defined.
The link for "paint order" doesn’t seem right. Is there a more canonical definition?
This needs proper types.
"DOM manipulation task source" doesn’t link due to a bikeshed bug.
The link for "paint order" doesn’t seem right. Is there a more canonical definition?
Refactor this so the algorithm takes a set of elements that will be captured. This centralizes the logic for deciding if an element should be included or not.
Specify what happens with mix-blend-mode.
The prose around "effect target" is incorrect, but #8001 needs to land before it can be fixed.
This prose isn’t quite right, but it’s blocked on #8004.
Define the algorithm used to clip the snapshot when it exceeds max size.
Isolation and the dynamic setting of blending is only necessary to get the right cross-fade between new and old image pixels. Would it be simpler to always add it and try to optimize in the implementation?