View Transitions API Performance
This guide is part of Animation & Compositing Performance, within Rendering & CSS Performance. The View Transitions API animates between two states of a page — or between two pages — with a few lines of code. For same-document transitions, document.startViewTransition(callback) captures a snapshot of the current state, runs your callback to update the DOM, captures the new state, and animates between them with CSS. For cross-document transitions, the @view-transition { navigation: auto; } rule enables the same effect across same-origin navigations in supporting browsers.
The API is efficient in that the animation itself runs on snapshots, typically on the compositor. But the transition has costs around it: capturing snapshots requires rendering, the page is non-interactive while the transition runs, many named elements multiply snapshot work and memory, and cross-document transitions can delay the moment the new page is shown. Used carelessly, view transitions can make INP and perceived navigation speed worse.
Rapid Diagnosis
- Record a trace of the interaction that triggers the transition; note time from input to first animated frame.
- Count elements with
view-transition-nameat the time of the transition. - Check the update callback: does it fetch data or do heavy work before resolving?
- Measure transition durations; long animations keep the page non-interactive.
Root Cause Analysis
1. Slow update callbacks. The new state cannot be captured until the callback resolves, delaying the first frame after input.
2. Many named elements. Each named element is captured and animated separately, increasing rendering and memory cost.
3. Long animations. Pointer events go to the transition overlay, not the page, while the transition runs.
4. Large snapshots. Full-page or very tall named elements produce large textures.
Step-by-Step Resolution
1. Keep the DOM update fast
async function showProduct(id) {
const data = await fetchProduct(id); // fetch BEFORE starting the transition
if (!document.startViewTransition) { render(data); return; }
document.startViewTransition(() => render(data)); // synchronous, fast update
}
// trade-off: fetching first delays the start of the transition, but the transition
// itself is short and the page stays interactive while data loads.
2. Limit named elements
Name only the elements that need to morph (a thumbnail becoming a hero, a title moving). Let the rest of the page cross-fade as the root snapshot. Avoid naming every list item; if you must, name only items visible in the viewport.
3. Keep transitions short
Use 150–300ms for UI transitions. Set durations on ::view-transition-group(*) and specific groups.
::view-transition-group(*) { animation-duration: 220ms; }
@media (prefers-reduced-motion: reduce) { ::view-transition-group(*) { animation: none; } }
/* trade-off: shorter transitions look snappier and return interactivity sooner,
but very short ones can feel abrupt for large layout changes. */
4. For cross-document transitions, avoid blocking rendering
Cross-document transitions wait for the new page to be ready to render. Keep the new page's critical path fast (critical CSS, no render-blocking scripts), and avoid using <link rel="expect" blocking="render"> on elements far down the page, which delays the first frame.
Verification
Record the interaction: the time from input to the first transition frame should be short (under 100ms) and no long task should precede it. Count named elements during the transition in DevTools (the Elements panel shows ::view-transition pseudo-elements). Check INP for the triggering interaction in the field, and for cross-document transitions, compare LCP and FCP of navigations with and without the transition.
Worked Example: A Photo Gallery
A photo gallery app used a view transition to morph thumbnails into a full-size viewer. The callback awaited image data and decoding inside startViewTransition, and every thumbnail on the page had a view-transition-name. Opening a photo froze the page for 400–700ms before animating, and INP for thumbnail taps was 620ms. The team fetched and decoded the image (img.decode()) before starting the transition, and assigned a view-transition-name only to the tapped thumbnail and the viewer image, just before the transition. Time to the first animated frame fell to 45ms, and INP p75 for thumbnail taps to 140ms.
Cross-Document Transitions and Navigation Speed
Cross-document view transitions make multi-page sites feel like apps, but they couple the old and new page. The old page's snapshot is held until the new page renders its first frame, then the transition animates. If the new page is slow to render, users see the old page frozen rather than a blank screen — arguably better, but the delay is the same. If the transition adds render-blocking requirements (waiting for specific elements), it can delay first paint. Measure navigation timings with and without the transition; combine with speculation rules (prerendering) to make the new page ready instantly, which is where cross-document transitions look and perform best.
Measuring Transition Cost
To see what a transition costs, record two traces of the same interaction — one with startViewTransition and one with the update applied directly. Compare the time from input to the next frame, the rendering work during capture, and the overall time until the page accepts input again. In the field, tag interactions that triggered a transition in your INP reporting, so you can compare INP for transitioned and non-transitioned updates. If transitions add more than a few tens of milliseconds before the first frame, reduce named elements or simplify the update.
Common Mistakes
- Doing async work inside the callback. The page freezes while it waits.
- Naming every element. Snapshot cost and memory grow with each named element.
- Long, elaborate animations. The page is non-interactive for their duration.
- Animating the LCP element on navigation. May delay when it is considered painted.
Edge Cases
Duplicate names. Two elements with the same view-transition-name abort the transition; ensure uniqueness at capture time.
Scroll position changes. The root snapshot covers the viewport; transitions involving large scroll changes may look odd and need custom handling.
Third-party content. Iframes are captured as images; their contents do not animate.
Low-memory devices. Many large snapshots can be expensive; keep named elements small.
FAQ
Do view transitions run on the compositor?
The animation of snapshot pseudo-elements typically uses transforms and opacity and can run on the compositor. Capturing snapshots and updating the DOM happen on the main thread.
Can the user interact during a view transition?
No. During the transition, the page is covered by the transition pseudo-elements, so input does not reach the page until it finishes.
Do view transitions affect INP?
They can. If the update callback is slow, the next paint after the interaction is delayed. A fast callback and short transition keep INP low.
Do cross-document transitions slow down navigation?
They add little overhead if the new page renders quickly. They do not make the new page load faster; combine them with prerendering for instant-feeling navigations.
How many named elements are reasonable?
A handful per transition. Each adds snapshot and animation work; name only what needs to morph.
Can I skip a transition?
Yes. Call transition.skipTransition() to jump to the end state, useful when a new navigation starts before the previous transition finishes.
Do view transitions work with frameworks?
Yes. Frameworks wrap state updates in startViewTransition, and several routers provide built-in integration. Keep the wrapped update synchronous and fast.
Should I use view transitions for every route change?
Not necessarily. They suit navigations where continuity helps users understand what changed, like opening an item from a list. For frequent or unrelated navigations, an instant change is often better.
Related
- Animating only transform and opacity — custom transition animations.
- Scroll-driven animations without JavaScript — the other modern animation API.
- Optimizing INP with scheduler.yield — keeping interactions responsive.