View Transition Snapshot Memory Cost
A view transition captures bitmaps of the old and new states and animates between them. Those bitmaps are a real allocation, held for the duration of the transition, and they arrive on top of whatever the page is already holding. On a heavy page that is the moment of peak demand — and it is also the moment the reader is watching most closely, which makes a discarded texture there particularly conspicuous. This guide covers what the allocation is and how to keep it modest. It sits under GPU memory and layer budgets.
When to use this approach
- A transition that flickers or shows a blank region mid-morph, especially on mobile. That is the allocation exceeding what the device will give.
- Before adding names to a transition. Each one has a cost, and the count is the thing to decide deliberately.
- On a page that is already near its layer budget. The transition is additive, so a page with little headroom is a page whose transitions will be the first thing to break.
Implementation
1. Count the names, not the elements
/* two names — four snapshots plus the root */
.article-hero { view-transition-name: hero; }
.article-title { view-transition-name: title; }
Every name produces one snapshot on each side. The root itself is also captured, which is what makes an unnamed transition still work as a crossfade.
2. Assign names at interaction time
function beginMorph(card) {
card.style.viewTransitionName = 'hero';
const t = document.startViewTransition(() => commit(card.dataset.route));
t.finished.finally(() => { card.style.viewTransitionName = ''; });
}
This keeps exactly one element named at a time, which satisfies the uniqueness requirement and means the allocation exists only during the transition.
3. Name small things, not large ones
A snapshot is sized by the element’s painted area. A hero image and a title cost a fraction of what a full-bleed section costs, and they carry almost all of the continuity the effect is trying to express.
4. Leave headroom for the transition
If the page is at 90 MB at rest and the transition allocates 30 MB, the peak is 120 MB — and the peak is what has to fit. Budgeting only the resting state is how a page that measures well still breaks during navigation.
5. Reduce the transition on constrained viewports
@media (max-width: 40rem) {
/* keep the root crossfade, drop the named morph */
.article-hero { view-transition-name: none; }
}
The navigation still animates; it simply animates more cheaply. This is a better degradation than disabling transitions entirely, which loses the change signal.
Verification
Watch a transition on a real device with the page’s heaviest section loaded. A blank or half-drawn region during the morph is the allocation exceeding the budget, and it will not appear on a development machine.
Measure the layer figure immediately before triggering a transition, then estimate the snapshot cost from the named elements’ dimensions. The sum is the peak, and it is the number to compare against the device class you support.
Confirm names are cleared afterwards. A name left on an element persists into the next navigation, which both risks a duplicate-name collision and can hold a snapshot longer than intended.
Finally, test a navigation immediately after another one. Back-to-back transitions are where allocations can overlap, and it is a path that manual testing rarely exercises.
Edge cases and gotchas
A name left in place after the transition. The next navigation finds two elements claiming it, and the transition is skipped entirely — with no console output.
Naming a scrollable container. The snapshot captures what was rendered, which for a scrolled container is its visible portion. The result is frequently not what the author expected, and it is a large allocation for a confusing effect.
Cross-document transitions and snapshot lifetime. The outgoing document’s snapshots must survive its teardown, so the allocation spans the navigation gap. A slow destination page holds the memory longer.
Nested named elements. Naming both a card and something inside it produces separate groups with separate snapshots, and the inner one animates independently of its visual parent. This is occasionally the intent and usually a surprise.
Transitions on pages with heavy background media. The root snapshot includes the whole viewport, so a page with a full-bleed hero pays for it in every transition whether anything is named or not.
Browser-specific notes
Snapshot allocation is not specified in memory terms, so each engine sizes and manages it differently. What is consistent is the shape of the cost: two snapshots per name plus a root capture.
Chromium tends to degrade by dropping frames when the allocation is tight, which shows as a stuttering morph. Safari on iOS discards textures instead, which shows as a blank region — the same divergence described in the WebKit notes and, again, the reason a page tuned only in Chromium can break there.
Firefox, with same-document transitions behind a flag in current stable builds, mostly does not exercise this path at all. That makes it a poor place to test transition memory and a good place to verify that the unsupported fallback is well behaved.
Frequently Asked Questions
How many names is too many?
More than about five is worth questioning, and more than ten is almost certainly naming things that do not need it. The number is driven by design rather than by memory: a good transition expresses continuity for one or two elements while everything else crossfades, and a transition that morphs ten things simultaneously reads as busy regardless of what it costs.
Does the snapshot include child elements?
Yes — a snapshot is a picture of the element as painted, including everything inside it. That is why naming a container captures its children too, and why naming both a container and a child produces two overlapping captures of largely the same pixels.
Is the allocation released immediately?
It is released when the pseudo-element tree is torn down, which happens as the transition finishes. On a page navigating rapidly, allocations from consecutive transitions can overlap briefly, which is the case worth testing explicitly.
Can I measure the snapshot cost directly?
Not directly — there is no API and the layer panels do not attribute it separately. Estimating from the named elements’ painted dimensions, using the same formula as any other texture, gets close enough to inform the decision, which is all the number needs to do.
Should a transition be disabled entirely on constrained devices?
Rarely. The root crossfade is cheap — it is one capture of the viewport, which the page has effectively already rasterised — and it carries the change signal that makes a navigation comprehensible. What is worth dropping on constrained devices is the named morphs, which are the expensive part and the least essential.
Disabling transitions outright also loses the accessibility benefit of a visible change indicator, and replaces a cheap effect with a hard cut for no memory saving worth having.
Does the snapshot cost depend on what is inside the element?
Only through its painted area. A card containing a photograph and a card containing text of the same dimensions cost the same to snapshot, because both are captured as bitmaps of the same size. What differs is what the snapshot looks like mid-morph: text resamples visibly under scaling, which is a visual argument for naming image-led elements rather than text-led ones.
Does the root snapshot count against the budget too?
Yes, and it is the one allocation you cannot avoid — a transition captures the viewport whether or not anything is named. On a page with a full-bleed hero that capture is large, which is why transition memory on media-heavy pages starts high before any naming decision is made. It is also an argument for keeping the transition itself brief on such pages, since the allocation lasts exactly as long as the animation does.
Related
- How @view-transition works under the hood — the snapshot lifecycle in detail
- Cross-Route Element Morphing — choosing which element carries the name
- GPU Memory & Layer Budgets — the budget this allocation lands on top of
- Safari and WebKit performance notes — where exceeding it looks like a blank frame