View Transitions API in Practice: Cross-Page and Cross-Document Animated Transitions
The Evolution of Page Transitions
| Approach | Scope | Complexity | Browser Native | Performance |
|---|---|---|---|---|
| CSS transition | Single component | Low | ✅ | High |
| Framer Motion / GSAP | SPA routing | High | ❌ | Medium |
| Shared Element Transition | SPA + MPA | Low | ✅ | High |
| View Transitions API | SPA + MPA | Low | ✅ | High |
The View Transitions API lets the browser automatically handle old→new state snapshots, animations, and cleanup. Developers only declare "when to transition" and "how to animate."
View Transitions in SPA
Basic Usage: document.startViewTransition
function navigateTo(newRoute: string) {
if (!document.startViewTransition) {
updateDOM(newRoute);
return;
}
document.startViewTransition(() => {
updateDOM(newRoute);
});
}
The browser automatically:
- Captures the old state snapshot (
::view-transition-old) - Executes the callback, updating DOM
- Captures the new state snapshot (
::view-transition-new) - Runs a cross-fade animation between the two snapshots
Custom Transition Animations
Default Cross-Fade
::view-transition-old(root) {
animation: 0.3s ease-in both fade-out;
}
::view-transition-new(root) {
animation: 0.3s ease-out both fade-in;
}
Slide Transition Effect
::view-transition-old(root) {
animation: 0.4s ease-in both slide-to-left;
}
::view-transition-new(root) {
animation: 0.4s ease-out both slide-from-right;
}
@keyframes slide-to-left {
to { transform: translateX(-100%); }
}
@keyframes slide-from-right {
from { transform: translateX(100%); }
}
Shared Element Transitions: Cross-Component Animation
Set view-transition-name on elements, and the browser automatically performs FLIP animation between old→new positions:
.card-image {
view-transition-name: card-img;
}
.card-title {
view-transition-name: card-title;
}
Practice: List→Detail Hero Animation
function openDetail(cardId: string) {
document.startViewTransition(() => {
document.querySelector('.list-page')!.style.display = 'none';
document.querySelector('.detail-page')!.style.display = 'block';
document.querySelector(`#card-${cardId} img`)!
.style.viewTransitionName = 'hero-image';
document.querySelector('.detail-hero img')!
.style.viewTransitionName = 'hero-image';
});
}
The list card image → detail page hero image automatically animates with smooth scale + position transitions.
MPA Cross-Document Transitions
Chrome 111+ supports cross-document (cross-navigation) View Transitions:
Enable Cross-Document Transitions
<meta name="view-transition" content="same-origin">
One meta tag enables automatic cross-fade for same-origin navigations!
Custom CSS with Navigation Direction
@media (navigation: forward) {
::view-transition-old(root) { animation: slide-to-left; }
::view-transition-new(root) { animation: slide-from-right; }
}
@media (navigation: back) {
::view-transition-old(root) { animation: slide-to-right; }
::view-transition-new(root) { animation: slide-from-left; }
}
Advanced Techniques
Excluding Elements from Transitions
header, footer {
view-transition-name: none;
}
Dynamic view-transition-name
document.querySelectorAll('.card').forEach((card, i) => {
card.style.viewTransitionName = `card-${i}`;
});
Wait for Image Load Before Transition
document.startViewTransition(async () => {
updateDOM(newRoute);
await document.querySelector('.detail-hero img')?.decode();
});
Integration with ToolsKu
- CSS Animation Tool: Generate @keyframes for View Transitions
- Gradient Generator: Create gradient backgrounds for transition overlay layers
- Image Collage: Use shared element transitions for collage layout switching
Performance Considerations
| Aspect | Recommendation |
|---|---|
| Snapshot size | Avoid setting view-transition-name on large elements |
| Transition duration | 200-400ms is the perceptually optimal range |
| Concurrent transitions | Multiple shared elements animating simultaneously increases GPU load |
| Layout thrashing | Avoid triggering layout reflows in the callback, or snapshots will be inaccurate |
Common Questions
Can I update DOM asynchronously in the startViewTransition callback?
Yes. The callback supports async functions—the browser waits for the Promise to resolve before capturing the new snapshot. However, the old snapshot remains visible during the wait; excessive waiting causes a jank sensation.
Difference between cross-document and SPA transitions?
SPA uses document.startViewTransition() for manual triggering; MPA uses <meta name="view-transition"> for automatic triggering. Both share the same CSS pseudo-element and animation definition system.
How to debug View Transitions?
Chrome DevTools → Elements panel shows the ::view-transition-group pseudo-element tree. The Animation panel allows frame-by-frame transition debugging.
Summary
The View Transitions API transforms page transitions from manual FLIP calculations to browser-native snapshots + animations, dramatically reducing implementation complexity. SPA uses document.startViewTransition(), MPA uses meta tags, and shared elements use view-transition-name—three scenarios unified under one CSS pseudo-element system. This is the optimal solution for modern web application page transitions.
Try these browser-local tools — no sign-up required →