Summary
The View Transitions API lets the browser crossfade or morph elements between two DOM states using `document.startViewTransition()` (same-document) or the `@view-transition` at-rule (cross-document MPA). Scroll-driven animations bind `animation-timeline` to scroll or element position — no JS, no `IntersectionObserver`. Same-document transitions are widely supported; cross-document transitions and scroll-driven animations are newer — check Baseline/caniuse before relying on them.
Jump to the interview angleView Transitions & Scroll Animations
View Transitions capture a screenshot of the current DOM state, apply a new state, then animate between the two snapshots using ::view-transition-old and ::view-transition-new pseudo-elements. Same-document transitions use document.startViewTransition(); cross-document (MPA) transitions need only @view-transition { navigation: auto; } in CSS — no JS at all. Scroll-driven animations bind animation-timeline to scroll progress, moving animation playback in sync with the user's scroll, on the compositor thread.
Same-document transition + scroll-driven reveal
Assign view-transition-name to the element that should morph. Wrap the DOM update in startViewTransition. For scroll animations, set animation-timeline: view() and use animation-range to control when in the scroll the animation plays.
/* ─── View transition ─────────────────────────────── */
/* Opt the hero image into a named layer so it morphs. */
.hero-image {
view-transition-name: hero;
}
/* Override the default crossfade with a slide. */
::view-transition-old(hero) {
animation: slide-out 0.3s ease-in forwards;
}
::view-transition-new(hero) {
animation: slide-in 0.3s ease-out forwards;
}
@keyframes slide-out { to { transform: translateX(-100%); opacity: 0; } }
@keyframes slide-in { from { transform: translateX(100%); opacity: 0; } }
/* Cross-document MPA: one rule, no JS needed. */
@view-transition {
navigation: auto;
}
/* ─── Scroll-driven animation ──────────────────────── */
.card {
opacity: 0;
translate: 0 2rem;
animation: fade-up linear both;
/* Bind to how much of .card is in the viewport. */
animation-timeline: view();
/* Play when the element is 10%–40% visible. */
animation-range: entry 10% entry 40%;
}
@keyframes fade-up {
to { opacity: 1; translate: 0 0; }
}
/* ─── Reduced motion ───────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
::view-transition-old(*),
::view-transition-new(*) {
animation: none;
}
.card {
animation: none;
opacity: 1;
translate: 0 0;
}
}view-transition-name creates a named capture pair. animation-timeline: view() needs no scroll event listener — the browser drives playback on the compositor. The prefers-reduced-motion block disables both APIs in one place.
Same-document vs cross-document transitions
| Feature | Same-document | Cross-document MPA | |
|---|---|---|---|
| Trigger | `document.startViewTransition(callback)` | `@view-transition { navigation: auto; }` | |
| JS required | Yes — wraps the DOM mutation | No — CSS only | |
| Browser support | Widely supported (Baseline) | Chromium + Safari; Firefox not yet — check caniuse | |
| Scope | SPA route changes, modals, in-page updates | Full page navigations between URLs |
Reduced motion and unique names
Every view-transition-name on the page must be unique at the moment the transition fires; duplicates silently skip the morph. Always wrap transition overrides in @media (prefers-reduced-motion: reduce) and set animation: none on both ::view-transition-old(*) and ::view-transition-new(*) — the browser does not suppress them automatically.
Interview angle
Interviewers ask about MPA cross-document support, the ::view-transition-* pseudo-element tree, and why scroll-driven animations beat IntersectionObserver for paint-free effects. Soundbite: "startViewTransition snapshots the old and new state; the browser runs the crossfade on the compositor — zero JS animation loop needed."
Key terms
- view-transition-name
- CSS property that opts an element into a named transition layer, pairing old and new states for morphing.
- ::view-transition-old / ::view-transition-new
- Pseudo-elements holding the captured screenshot of the outgoing and incoming state during a transition.
- animation-timeline: scroll()
- Links an animation's progress to a scroll container's scroll position; no JS required.
- animation-timeline: view()
- Links animation progress to how much of an element is visible inside a scroll container.
- @view-transition
- CSS at-rule that enables cross-document (MPA) view transitions without any JavaScript.