---
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
<!-- old.html, new.html — 둘 다 -->
<meta name="view-transition" content="same-origin">
```

```css
@view-transition {
  navigation: auto;
}
```

→ Anchor click → 새 page 로 transition (MPA 도).

→ 2024 Chrome 126+ 지원.

### Astro / Next 통합
```astro
---
// Astro view transitions
import { ViewTransitions } from 'astro:transitions';
---
<head>
  <ViewTransitions />
</head>
```

```tsx
// Next.js (App Router)
// Layout 에 추가.
<head>
  <meta name="view-transition" content="same-origin" />
</head>
```

→ 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]]
