7.2 KiB
7.2 KiB
id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
| id | title | category | status | source_trust_level | verification_status | created_at | updated_at | tags | tech_stack | applied_in | aliases | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| web-view-transitions-cross-doc | View Transitions API — same-doc / cross-doc | Coding | draft | B | conceptual | 2026-05-09 | 2026-05-09 |
|
|
|
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)
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 가 자동:
- 현재 snapshot.
- Callback 실행.
- 새 snapshot.
- Crossfade (default).
CSS (default)
::view-transition-old(root) {
animation: 0.3s fade-out;
}
::view-transition-new(root) {
animation: 0.3s fade-in;
}
→ root = 페이지 전체.
매칭된 element (morph)
.thumbnail {
view-transition-name: hero-img;
}
/* Detail page */
.full-image {
view-transition-name: hero-img;
}
→ Same name = morph (Hero animation).
Custom CSS animation
@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)
<!-- old.html, new.html — 둘 다 -->
<meta name="view-transition" content="same-origin">
@view-transition {
navigation: auto;
}
→ Anchor click → 새 page 로 transition (MPA 도).
→ 2024 Chrome 126+ 지원.
Astro / Next 통합
---
// Astro view transitions
import { ViewTransitions } from 'astro:transitions';
---
<head>
<ViewTransitions />
</head>
// Next.js (App Router)
// Layout 에 추가.
<head>
<meta name="view-transition" content="same-origin" />
</head>
→ MPA 가 SPA 같은 UX.
TanStack Router / React Router
// Manually trigger
const navigate = useNavigate();
const handleClick = async () => {
if (document.startViewTransition) {
document.startViewTransition(() => {
flushSync(() => navigate('/profile'));
});
} else {
navigate('/profile');
}
};
React 19 + view transitions
import { useViewTransitionState } from 'react-router';
const isTransitioning = useViewTransitionState('/profile');
// → CSS class 추가 가능
Skip transition (specific case)
const transition = document.startViewTransition(() => render(newState));
if (!important) {
transition.skipTransition(); // 즉시 끝남
}
State-based animation
// List item delete + re-render
async function deleteItem(id) {
document.startViewTransition(() => {
items = items.filter(i => i.id !== id);
rerender();
});
}
→ List item 가 자연 animate.
Dynamic name
// 매 element 가 unique name
listItems.forEach((item, i) => {
item.style.viewTransitionName = `item-${item.id}`;
});
함정: 같은 name 두 element
.a, .b { view-transition-name: same; }
/* → conflict, transition 깨짐 */
Old / new pseudo-element
::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
document.startViewTransition(async () => {
await updateA();
await updateB();
});
→ Promise resolve = transition 끝.
Async render
document.startViewTransition(async () => {
const data = await fetch('/data').then(r => r.json());
render(data);
});
→ Network 동안 사용자 가 "frozen" 안 보고 (snapshot 후 wait).
→ 너무 느림 = bad UX.
Reduce motion (a11y)
@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
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
.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.