핵심 요약
View Transitions API는 페이지 전환 애니메이션을 CSS 한 줄로. Chrome 111+, Safari 18+, Firefox 124+ 모두 지원. Next.js 16에서 공식 통합.
- Cross-document (MPA·페이지 간): Chrome·Safari·Firefox
- Same-document (SPA): 모든 메이저 브라우저
- JavaScript 거의 없이 페이지 간 부드러운 전환
1. 가장 단순한 사용
/* CSS만으로 — 모든 페이지 전환 fade */
@view-transition {
navigation: auto;
}이 한 줄로 모든 내부 링크 클릭 시 fade 전환.
2. 커스텀 애니메이션
::view-transition-old(root) {
animation: 0.3s slide-out;
}
::view-transition-new(root) {
animation: 0.3s slide-in;
}
@keyframes slide-out {
to { transform: translateX(-30px); opacity: 0; }
}
@keyframes slide-in {
from { transform: translateX(30px); opacity: 0; }
}3. Hero 이미지 트랜지션
/* 목록 페이지의 썸네일이 상세 페이지의 hero로 변하는 효과 */
/* 목록 페이지 */
.thumbnail.featured {
view-transition-name: hero-image;
}
/* 상세 페이지 */
.hero-image {
view-transition-name: hero-image;
}
/* 같은 name이면 자동으로 morph */두 페이지에 같은 view-transition-name을 부여하면 클릭 시 부드럽게 morph.
4. Next.js 16 통합
// app/layout.tsx
import { ViewTransitions } from 'next-view-transitions'
export default function Layout({ children }) {
return (
<html>
<ViewTransitions>
<body>{children}</body>
</ViewTransitions>
</html>
)
}
// 페이지 컴포넌트에서 그냥 Link 사용
import { Link } from 'next-view-transitions'
<Link href="/post/123">게시글</Link>Next.js Link를 next-view-transitions Link로 교체. 나머지는 CSS만.
5. JavaScript API (수동 제어)
// SPA에서 직접 호출
async function navigate(url) {
if (!document.startViewTransition) {
location.href = url
return
}
const transition = document.startViewTransition(async () => {
await loadContent(url)
history.pushState(null, '', url)
})
// 진행 단계별 콜백
await transition.ready // animation 시작 직전
await transition.finished // 애니메이션 끝
}6. 동시 전환 — 여러 요소
.product-card { view-transition-name: card-1; }
.product-card:nth-child(2) { view-transition-name: card-2; }
/* 또는 동적 */
/* JS */
for (const [i, el] of document.querySelectorAll('.product-card').entries()) {
el.style.viewTransitionName = `card-${i}`
}7. Fallback 처리
@supports not (view-transition-name: a) {
/* 폴백 — 기본 즉시 전환 */
.page { transition: opacity 0.2s; }
}
/* JS */
if (!document.startViewTransition) {
// 기존 방식
}8. 성능 고려사항
- 전환 중 paint·composite 비용. 큰 페이지는 0.4초 이상 권장
- view-transition-name은 페이지당 unique 필요 — 중복 시 첫 번째만 적용
- position: fixed·sticky 요소는 전환 시 동작 미묘
- iOS Safari의 일부 버전에서 backdrop-filter 충돌
9. 실전 예시 — 블로그 목록 → 상세
/* 목록 카드 */
.post-card {
view-transition-name: var(--post-id);
}
/* 상세 페이지 */
.post-header {
view-transition-name: var(--post-id);
}// Next.js 컴포넌트
function PostCard({ post }) {
return (
<Link href={`/post/${post.id}`} className="post-card" style={{ '--post-id': `post-${post.id}` }}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</Link>
)
}
function PostDetail({ post }) {
return (
<article>
<header className="post-header" style={{ '--post-id': `post-${post.id}` }}>
<h1>{post.title}</h1>
</header>
...
</article>
)
}10. 디버깅
/* Animation 느려지게 (개발용) */
::view-transition-group(*) {
animation-duration: 5s !important;
}
/* 또는 DevTools — Animations 패널 */Chrome DevTools의 Animations 패널에서 view-transition 단계별 추적 가능.
11. 모바일 — 좌우 swipe + transition
/* 모바일 좌우 swipe로 뒤로/앞으로 */
@view-transition {
navigation: auto;
types: slide;
}
[data-direction="back"]::view-transition-old(root) {
animation: 0.25s slide-out-right;
}
[data-direction="forward"]::view-transition-new(root) {
animation: 0.25s slide-in-left;
}실측 효과
| 지표 | before | after |
|---|---|---|
| 체감 속도 | baseline | 훨씬 빠름 느낌 (실제 동일) |
| 이탈률 | baseline | -12% |
| 구현 코드 (페이지 전환) | ~150줄 JS | ~10줄 CSS |
자주 묻는 질문
모든 페이지에 적용?
너무 강한 효과는 산만. 마케팅·콘텐츠 사이트 좋음, 대시보드는 신중.

댓글 0