--- id: web-view-transitions-cross-doc title: View Transitions API — same-doc / cross-doc category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [web, animation, vibe-coding] tech_stack: { language: "TS / CSS", applicable_to: ["Frontend"] } applied_in: [] aliases: [View Transitions, startViewTransition, view-transition-name, cross-doc, MPA transition, navigation API] --- # View Transitions API > SPA 의 fancy transition 가 표준 web. **Same-doc (SPA) 과 cross-doc (MPA) 둘 다**. Browser 가 magic. ## 📖 핵심 개념 - 두 state snapshot. - 매칭된 element 가 morph. - CSS animation 가 매 transition pair. - Native browser support. ## 💻 코드 패턴 ### Same-doc (SPA) ```ts async function navigate(newContent) { if (!document.startViewTransition) { // Fallback document.body.innerHTML = newContent; return; } const transition = document.startViewTransition(() => { document.body.innerHTML = newContent; }); await transition.finished; } ``` → Browser 가 자동: 1. 현재 snapshot. 2. Callback 실행. 3. 새 snapshot. 4. Crossfade (default). ### CSS (default) ```css ::view-transition-old(root) { animation: 0.3s fade-out; } ::view-transition-new(root) { animation: 0.3s fade-in; } ``` → `root` = 페이지 전체. ### 매칭된 element (morph) ```css .thumbnail { view-transition-name: hero-img; } /* Detail page */ .full-image { view-transition-name: hero-img; } ``` → Same name = morph (Hero animation). ### Custom CSS animation ```css @keyframes slide-in { from { transform: translateX(100%); } } @keyframes slide-out { to { transform: translateX(-100%); } } ::view-transition-old(root) { animation: 0.3s slide-out; } ::view-transition-new(root) { animation: 0.3s slide-in; } ``` ### Cross-doc (MPA) ```html ``` ```css @view-transition { navigation: auto; } ``` → Anchor click → 새 page 로 transition (MPA 도). → 2024 Chrome 126+ 지원. ### Astro / Next 통합 ```astro --- // Astro view transitions import { ViewTransitions } from 'astro:transitions'; --- ``` ```tsx // Next.js (App Router) // Layout 에 추가. ``` → MPA 가 SPA 같은 UX. ### TanStack Router / React Router ```ts // Manually trigger const navigate = useNavigate(); const handleClick = async () => { if (document.startViewTransition) { document.startViewTransition(() => { flushSync(() => navigate('/profile')); }); } else { navigate('/profile'); } }; ``` ### React 19 + view transitions ```tsx import { useViewTransitionState } from 'react-router'; const isTransitioning = useViewTransitionState('/profile'); // → CSS class 추가 가능 ``` ### Skip transition (specific case) ```ts const transition = document.startViewTransition(() => render(newState)); if (!important) { transition.skipTransition(); // 즉시 끝남 } ``` ### State-based animation ```ts // List item delete + re-render async function deleteItem(id) { document.startViewTransition(() => { items = items.filter(i => i.id !== id); rerender(); }); } ``` → List item 가 자연 animate. ### Dynamic name ```ts // 매 element 가 unique name listItems.forEach((item, i) => { item.style.viewTransitionName = `item-${item.id}`; }); ``` ### 함정: 같은 name 두 element ```css .a, .b { view-transition-name: same; } /* → conflict, transition 깨짐 */ ``` ### Old / new pseudo-element ```css ::view-transition /* root */ ::view-transition-image-pair(name) /* container */ ::view-transition-old(name) ::view-transition-new(name) ``` → Animatable. ### Group, image-pair, old, new ``` ::view-transition-group(name) — wrapper, position ::view-transition-image-pair(name) — old + new layered ::view-transition-old(name) — outgoing ::view-transition-new(name) — incoming ``` ### Nested transitions ```ts document.startViewTransition(async () => { await updateA(); await updateB(); }); ``` → Promise resolve = transition 끝. ### Async render ```ts document.startViewTransition(async () => { const data = await fetch('/data').then(r => r.json()); render(data); }); ``` → Network 동안 사용자 가 "frozen" 안 보고 (snapshot 후 wait). → 너무 느림 = bad UX. ### Reduce motion (a11y) ```css @media (prefers-reduced-motion: reduce) { ::view-transition-old(*), ::view-transition-new(*) { animation: none; } } ``` ### Browser support ``` - Chrome 111+ (same-doc) - Chrome 126+ (cross-doc) - Edge 동일 - Firefox: 안 (no plan) - Safari: 진행 중 → Progressive enhancement. Fallback graceful. ``` ### Use case ``` - Hero animation (list → detail) - Tab switch (smooth) - Modal open/close - Navigation (page change) - Theme toggle (color smooth) - Filter / sort animation ``` ### Theme toggle ```ts async function toggleTheme() { if (!document.startViewTransition) return setTheme(); const transition = document.startViewTransition(setTheme); await transition.ready; document.documentElement.animate( { clipPath: ['circle(0% at 0 0)', 'circle(150% at 0 0)'] }, { duration: 500, pseudoElement: '::view-transition-new(root)' } ); } ``` → Circle reveal — Twitter style. ### Performance ``` - 매 transition = 2 snapshot (hardware accelerated) - 큰 page 가 느림 (snapshot cost) - 매우 많은 element name = 폭발 → Hero element 만 name. 나머지 = root crossfade. ``` ### vs Framer Motion / GSAP ``` Library: - Cross-browser - 정밀 control - 큰 bundle Native View Transitions: - 0 bundle - Browser-native - 모든 element 가 native → Native 가 default. Library 가 specific 정밀. ``` ### Animation API combine ```css .box { view-transition-name: my-box; } ::view-transition-old(my-box) { animation: scale-down 0.3s ease, fade-out 0.3s; } ``` → 다중 animation property. ### Debugging ``` Chrome DevTools: - Animations panel. - 매 transition 가 timeline. - Pause / step. ``` ### Production tips ``` 1. Hero element 만 name (모든 element X). 2. Reduce motion 항상 존중. 3. Async render < 500ms (frozen 시간 길면 bad). 4. Fallback 가 있어야 (Firefox). 5. Cross-doc 가 Chrome 126+ 만. ``` ## 🤔 의사결정 기준 | 작업 | 추천 | |---|---| | List → detail morph | view-transition-name | | Page navigation MPA | Cross-doc transition | | Page navigation SPA | startViewTransition | | Theme toggle | Custom + clip-path | | 작은 element animate | Native CSS animation | | Cross-browser | Framer Motion | | Modal | View Transitions | | 정밀 timeline | GSAP | ## ❌ 안티패턴 - **같은 name 여러 element**: conflict. - **Async 가 길음 (>1s)**: frozen UX. - **모든 element 가 name**: 성능. - **Fallback 없음**: Firefox 깨짐. - **Reduce motion 무시**: a11y. - **Cross-doc + 다른 origin**: 안 됨. ## 🤖 LLM 활용 힌트 - View Transitions = native + CSS-driven. - Same-doc (Chrome 111+) / Cross-doc (Chrome 126+). - view-transition-name 가 morph. - Astro / Next 가 자체 wrapper. ## 🔗 관련 문서 - [[Frontend_View_Transitions_Deep]] - [[Frontend_Animation_Motion]] - [[Web_History_API_Routing]]