Under The Hood
frontend css View Transitions API (Chrome 111+, cross-document 126+)

View Transitions API: startViewTransition() Snapshot Trees

Last updated
Prerequisites:
CSS animations and pseudo-elements
Understanding of DOM updates and rendering lifecycle
  • css
  • view-transitions
  • animation
  • snapshot
  • dom
  • browser-api

Read at your depth

The practical view

document.startViewTransition(() => updateDOM()) takes a snapshot of the current page, runs your callback (which updates the DOM), snapshots the new state, and animates between them. The default animation is a crossfade. You can customize it with CSS pseudo-elements: ::view-transition-old(root) and ::view-transition-new(root). Give elements a view-transition-name to get individual morphing animations between old and new positions. Cross-document transitions work with @view-transition { navigation: auto; } on same-origin navigations. Keep the callback synchronous or return a promise so the browser knows when the new state is ready.

The same idea in other frameworks

react equivalent
React apps wrap route changes with startViewTransition around the setState that swaps the route tree.

Legacy vs modern

JS-driven enter/exit animations vs the View Transitions API

Hand-rolled enter/exit animations require two states and coordination; startViewTransition snapshots both states automatically and crossfades/morphs them.

before → after
Manual enter animation + exit animation
oldEl.classList.add('exit'); // ❌ OLD: manual exit
setTimeout(() => { swapDOM(); newEl.classList.add('enter'); }, 300);
startViewTransition snapshot morph
document.startViewTransition(() => { swapDOM(); }); // ✅ NEW: snapshots + morphs automatically

Interview gotchas

Context

This probes the core mechanism — that the API rasterizes both states into compositor snapshots and animates them, which is different from simply running JS animations on live DOM.

The mechanical answer

Before running the callback, the engine performs a capture pass: it renders the current page and rasterizes it into snapshot images (a root snapshot plus one per element with a view-transition-name). The callback then updates the DOM, and a second capture pass rasterizes the new state. The engine mounts a view-transition pseudo-element tree (::view-transition-old/new) in the top layer and animates these snapshots — old image sliding/crossfading out while the new image animates in. Because the animation plays on captured images, the actual DOM swap is hidden behind them; no blank flash can appear. The live DOM is already the new state underneath, so after the animation the snapshots are removed and the real content shows.

Trap

The naive answer is 'it delays the DOM update until the animation ends'. The DOM updates during the callback; the animation runs over snapshots on top. Another trap: thinking every element animates individually by default — only elements with a view-transition-name get their own snapshot; everything else is part of the root crossfade. Candidates who explain the capture/commit/crossfade phases show real understanding.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.