Command Palette

Search for a command to run...

Hectal
Shots in this scene
SHOT 11.1Intermediate ~17 min · shot 1 of 7

The Snapshot Model

In one line

document.startViewTransition(update) photographs the page, runs your update while the screen shows the photograph, photographs the result, then animates between the two pictures. The default is a 250 ms cross-fade of the whole page.

0/7 · 0%

Think of it like this

A magician's assistant stands in front of a photograph of the stage while the crew changes the set behind it. When the set is ready the photograph is lifted away. The audience sees a smooth hand-over from the old picture to the new one, never the change itself.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

snapshot
A bitmap picture of the page or of one named element. You cannot click it.
pseudo-element tree
The temporary ::view-transition elements the browser builds for the animation.
update callback
The function you pass in. It changes the DOM; the new snapshot comes after it.
root
The default name for everything with no name of its own: the page itself.

Step by step

01Capture the old page

The browser photographs the page as it is now. Nothing has moved.

Capture the old pagediagram
Rendering diagram…

02Run your update with rendering paused

The screen keeps showing the old picture. A slow update means a frozen page.

03Capture the new page and build the tree

Slow the first example to 0.1× and press Swap: html::view-transition-group(root), new(root) and old(root) tracks appear, 250 ms each. The card has no track of its own.

04Animate, then give the page back

When finished fulfills the pseudo-elements are removed and the real page is clickable again.

Try it yourself

  1. 1

    Make the update slow

    Change the update to async () => { await new Promise((r) => setTimeout(r, 1000)); card.classList.toggle("b"); }. The page freezes for about a second, then the cross-fade runs. The timeline cannot step through the wait: its clock moves with frames, and frames are paused.

Code & diagrams

Try it: interactive

The four phases and the pseudo-element tree

Step through capture, update, capture and animate, then scrub to watch the old picture fade out and the new one fade in.

The three steps and the three promises

Press Pause, then Swap, then Step: the tracks appear. The first line is the feature check.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

startViewTransition is a function
update ran
updateCallbackDone
ready
finished
When the update throws or the API is missing

Each line settles all three promises. A throwing update rejects all three with its error, and the DOM change made before the throw stays. The last line is the fallback: no API, so run the update.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

fine | updateCallbackDone fulfilled | ready fulfilled | finished fulfilled
throws | updateCallbackDone rejected Error: boom | ready rejected Error: boom | finished rejected Error: boom
text after the throw: half-done
fallback returns: undefined, text: fallback

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Call the API with no guard in a browser without it

Add document.startViewTransition = undefined; as the first line, remove the typeof line and call document.startViewTransition(() => card.classList.toggle("b"));.

terminal
$ Run it
── what you'll see ──
Uncaught TypeError: document.startViewTransition is not a function
The card never changes: only the API ever calls the update.

Myth vs fact

Myth

A view transition animates the elements in my DOM.

Fact

It animates pictures of them. The real elements are already in their final place while the pictures move, which is why you cannot click a moving thing and why stretched text looks soft (Topic 11.2).

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Snapshots are bitmaps held in memory for every named element: name what must move and leave the rest to root.

  • ▸

    finished rejects only if the update throws or rejects. A skipped or replaced transition still fulfills it. ready is the promise that rejects for skips (Topic 11.5).

Remember this

  1. 1

    The problem this solves: the DOM changes in one frame. To animate old to new you used to keep both versions alive, place them by hand and clean up: hard for a reordered list, impossible for a removed element, very hard across two pages. A view transition keeps the old version as a picture, so you only write the change.

  2. 2

    The three steps. You call document.startViewTransition(update). (1) The browser captures the old state. (2) It runs your update with rendering paused: the screen keeps showing the old picture. (3) It captures the new state, builds a tree of pseudo-elements holding both pictures, and animates. A snapshot is a bitmap, not live DOM: it ignores clicks, and a video in it is frozen.

  3. 3

    The default is a cross-fade of the whole page: ::view-transition-old(root) fades out while ::view-transition-new(root) fades in over 250 ms, blended with plus-lighter so the middle does not dip. Measured in Chromium 153: five tracks, all 250 ms. The tree covers the page while it runs: document.elementFromPoint over a button returned HTML, then BUTTON afterwards.

  4. 4

    Three promises, in this order: updateCallbackDone (your update's promise fulfilled), ready (the tree exists, the animation is about to start), finished (the animation is over, the page is live again).

  5. 5

    The page is frozen while your update waits. Measured: requestAnimationFrame produced one frame in 0.9 s while an update waited 1 s. After about 4 seconds ready rejects with a TimeoutError (the DOM change still happens, finished fulfills; measured with a 5 s update). Fetch data before you call startViewTransition, not inside it.

  6. 6

    It is an enhancement. Same-document transitions work in about 92.9% of browsers (Chrome 111, Safari 18, Firefox 144; caniuse, 2026-10-19). Elsewhere the call throws a TypeError and your change never happens, so every call is guarded: if (!document.startViewTransition) { update(); return; }.

Explain it without notes

01

Walk through what the browser does between startViewTransition(update) and the animation starting, and say where your code runs.

02

Why must every call have a fallback, and what goes wrong if the update does await fetch(url)?

Practice

01

Write withTransition(update): a transition when supported, plain otherwise, no unhandled rejection for a skipped one.

02

Make a card toggle cross-fade and log done only when the animation has finished.

Done when you can

  • You can list the three steps and say where your update runs

  • You can read the default tracks and name the three promises

  • You can guard every call and keep updates short