---
id: frontend-view-transitions-deep
title: View Transitions API — 페이지 / 요소 transition
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [frontend, view-transitions, animation, vibe-coding]
tech_stack: { language: "TS / CSS", applicable_to: ["Frontend"] }
applied_in: []
aliases: [View Transitions API, MPA transitions, SPA transitions, view-transition-name, cross-document]
---
# View Transitions API
> Page / view 변경의 native transition. **Same-document (SPA) + Cross-document (MPA)**. Chrome 111+ / Safari 18+ / Firefox 141+. CSS-only 가 가능.
## 📖 핵심 개념
- Transition: snapshot → 새 view → 자동 cross-fade.
- view-transition-name: element 별 morph.
- Same-document: SPA 안.
- Cross-document: 페이지 navigation.
## 💻 코드 패턴
### Same-document (SPA)
```ts
async function navigate(url: string) {
if (!document.startViewTransition) {
return router.go(url); // fallback
}
const t = document.startViewTransition(async () => {
await router.go(url);
});
await t.finished;
}
```
```css
/* Default cross-fade */
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.3s;
}
```
### CSS-only fade
```css
@view-transition {
navigation: auto;
}
```
→ 모든 navigation 자동 fade. Chrome 126+.
### Element morph (shared element)
```html
```
→ 같은 name = 자동 morph (size, position).
```css
::view-transition-group(hero) {
animation-duration: 0.5s;
}
```
### React Router 7 / Next 통합
```tsx
// React Router 7
About
// Next.js (next/link)
import Link from 'next/link';
About
// + Chrome flag 또는 view-transition CSS
```
### Custom transition
```css
/* 슬라이드 in/out */
::view-transition-old(root) {
animation: 0.4s ease-out both slide-out-left;
}
::view-transition-new(root) {
animation: 0.4s ease-out both slide-in-right;
}
@keyframes slide-out-left {
to { transform: translateX(-100%); opacity: 0; }
}
@keyframes slide-in-right {
from { transform: translateX(100%); opacity: 0; }
}
```
### 다중 elements
```html
...
...
```
→ Page 간 이동 자동 morph. SPA framework 없이 OK.
### Fallback 처리
```ts
async function navigate(url: string) {
if (!('startViewTransition' in document)) {
return route(url); // 즉시
}
document.startViewTransition(() => route(url));
}
```
### Reduce motion
```css
@media (prefers-reduced-motion: reduce) {
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
}
}
```
### Promise / async
```ts
const transition = document.startViewTransition(async () => {
// DOM 변경
await fetchAndUpdate();
});
await transition.ready; // animation 시작 직전
await transition.finished; // 끝
```
### 사용 case
```
1. SPA 페이지 전환
2. List → detail (shared element)
3. Image gallery (큰 image morph)
4. Tab switching (smooth)
5. Modal 열기 / 닫기
6. Theme switch (큰 cross-fade)
```
### Theme switch 예
```ts
async function toggleTheme() {
const t = document.startViewTransition(() => {
document.documentElement.classList.toggle('dark');
});
await t.ready;
document.documentElement.animate({
clipPath: ['circle(0% at 100% 0%)', 'circle(150% at 100% 0%)'],
}, {
duration: 500,
pseudoElement: '::view-transition-new(root)',
});
}
```
→ Dark mode toggle 시 화면 wipe.
### Snapshot 비용
```
Browser 가 each transition 시 snapshot.
큰 page = 약간 느림.
→ 측정: Chrome DevTools Performance → Animation.
```
### 제어 (skip)
```ts
const t = document.startViewTransition(() => update());
// 사용자가 다른 거 누름 — 즉시 끝
button.onclick = () => t.skipTransition();
```
### 단일 element transition
```ts
// Modal 열기
async function openModal() {
document.startViewTransition(() => {
modalEl.style.viewTransitionName = 'modal';
modalEl.classList.add('open');
});
}
```
```css
::view-transition-new(modal) {
animation: 0.3s scale-in;
}
@keyframes scale-in {
from { transform: scale(0.8); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
```
### Multi-step
```ts
const t1 = document.startViewTransition(() => updateA());
await t1.finished;
const t2 = document.startViewTransition(() => updateB());
await t2.finished;
```
### Astro / Next 통합
```astro
---
// Astro
import { ViewTransitions } from 'astro:transitions';
---