View Transitions API — 2025 Baseline 달성 이후, 라이브러리 없이 구현하는 프로덕션 전환 애니메이션
솔직히 처음 이 API를 봤을 때 "이게 된다고?" 싶었습니다. Framer Motion이나 GSAP 없이, CSS 몇 줄과 JavaScript 메서드 하나만으로 앱 같은 화면 전환을 구현한다는 게 너무 좋아 보였거든요. 그런데 실제로 써보니 정말 됩니다. 브라우저가 화면 전환 전후 스냅샷을 컴포지터 레이어에서 다루기 때문에, transform·opacity 기반 애니메이션 구간에서는 꽤 부드러운 결과가 나옵니다.
2025년 10월, Firefox 144 출시로 Chrome, Edge, Safari, Firefox 4대 브라우저가 모두 document.startViewTransition()을 지원하게 됐습니다. 폴리필이나 "Chrome만 됩니다" 주석 없이 프로덕션에 쓸 수 있는 기준점이 된 거죠. 이 글을 쓰는 이유가 바로 여기 있습니다. 기존 MPA에 전환 애니메이션을 처음 도입하려는 분, 또는 Framer Motion 같은 라이브러리 의존성을 걷어내고 싶은 분이라면 지금이 딱 맞는 타이밍입니다.
이 글에서는 API 작동 원리부터 프레임워크별 통합 패턴, 그리고 실무에서 빠지기 쉬운 함정까지 한 번에 다룹니다. SPA, MPA, Next.js, Astro, SvelteKit 어느 환경에서 작업하든 바로 적용해볼 수 있는 내용들로 채웠습니다.
핵심 개념
Baseline Newly Available: W3C와 브라우저 벤더들이 공동으로 정의한 상태입니다. Chrome, Edge, Safari, Firefox 최신 버전 모두에서 동작한다는 의미로, "이제 써도 되는 단계"의 공식 기준점입니다. View Transitions API(same-document)는 2025년 10월 이 상태에 도달했습니다.
브라우저가 전환을 처리하는 세 단계
작동 원리는 생각보다 단순합니다. 복잡한 애니메이션 라이브러리처럼 DOM을 직접 조작하는 게 아니라, 브라우저가 "변하기 전"과 "변한 후"를 각각 스냅샷으로 찍고 그 사이를 채워줍니다.
document.startViewTransition(callback)호출 → 브라우저가 현재 화면을 스냅샷callback안에서 DOM 변경 수행- 브라우저가 이전 스냅샷 → 새 DOM 사이를 기본 크로스페이드 또는 커스텀 CSS 애니메이션으로 전환
// 원리를 보여주는 최소 예시 (실제 프로젝트에서는 클래스 토글이나 상태 업데이트를 활용하게 됩니다)
document.startViewTransition(() => {
container.innerHTML = newContent;
});기본값은 크로스페이드이고, CSS로 원하는 대로 커스터마이즈할 수 있습니다.
SPA 방식과 MPA 방식
API는 크게 두 가지 모드로 나뉩니다.
Same-document (SPA 방식): JavaScript에서 document.startViewTransition()을 직접 호출합니다. 2025년 10월 기준 Chrome 111+, Edge 111+, Safari 18+, Firefox 144+에서 완전 지원됩니다. Baseline Newly Available 상태이므로 프로덕션 적용에 적합합니다.
Cross-document (MPA 방식): CSS @view-transition at-rule만으로 동일 오리진의 두 페이지 사이에 전환 애니메이션을 적용합니다. JavaScript가 전혀 필요 없습니다. Chrome 126+, Edge 126+, Safari 18.2+에서 지원하지만 Firefox는 아직 미지원 상태입니다.
/* 두 페이지 모두에 이 두 줄만 추가하면 됩니다 */
@view-transition {
navigation: auto;
}공유 요소 전환 (Shared Element Transition)
저도 처음에 이게 이렇게 쉽게 된다는 걸 믿지 못했는데, View Transitions API의 진짜 매력이 여기 있습니다. view-transition-name CSS 속성으로 특정 요소에 이름을 붙이면, 화면 전환 시 그 요소가 독립적으로 움직입니다. 상품 카드에서 상품 상세 페이지로 이동할 때 이미지가 자연스럽게 날아가는 그 효과입니다.
/* 목록 페이지의 카드 이미지 */
.product-card img {
view-transition-name: product-hero;
}
/* 상세 페이지의 히어로 이미지 */
.product-detail .hero {
view-transition-name: product-hero;
/* 이미지 비율이 다를 때 늘어나는 현상 방지 */
object-fit: cover;
}
/* 전환 애니메이션 커스터마이즈 */
::view-transition-old(product-hero),
::view-transition-new(product-hero) {
animation-duration: 0.4s;
animation-timing-function: ease-in-out;
}두 페이지에서 같은 view-transition-name을 가진 요소가 발견되면, 브라우저가 알아서 그 사이를 보간해줍니다. 위치도, 크기도, 모양도요.
알아두면 좋은 것들 — Level 2 사양
W3C가 현재 활발히 개발 중인 Level 2 사양에는 실무에서 느꼈던 불편함을 해소해주는 기능들이 담겨 있습니다. 당장 쓸 수 있는 기능은 아니지만, 앞으로 어떻게 발전하는지 알아두면 도움이 됩니다.
view-transition-class: view-transition-name이 요소별 고유 ID라면, view-transition-class는 여러 요소에 공통 클래스를 부여해 동일한 애니메이션 스타일을 한 번에 적용합니다.
반복 요소의 자동 이름 생성: 카드 목록처럼 반복 요소마다 브라우저가 자동으로 내부 이름을 생성해주는 기능이 논의 중입니다. 현재 match-element, auto 등의 값으로 사양이 검토되고 있으며 아직 확정되지 않았습니다. 지금은 동적으로 생성된 리스트마다 유니크한 이름을 직접 부여해야 합니다.
:active-view-transition-type() 의사 클래스: 전환 타입에 따라 다른 CSS 애니메이션을 조건부로 적용할 수 있습니다. 아래 예시는 CSS nesting 문법(& 선택자)을 사용한 버전과 풀어 쓴 버전을 함께 담았습니다.
/* CSS nesting 문법 사용 */
:active-view-transition-type(slide-left) {
&::view-transition-old(root) { animation-name: slide-out-left; }
&::view-transition-new(root) { animation-name: slide-in-right; }
}
/* CSS nesting 없이 동일한 효과 */
:active-view-transition-type(slide-left)::view-transition-old(root) {
animation-name: slide-out-left;
}
:active-view-transition-type(slide-left)::view-transition-new(root) {
animation-name: slide-in-right;
}실전 적용
MPA에서 JavaScript 없이 페이지 전환 애니메이션
가장 빠르게 효과를 볼 수 있는 방법입니다. Astro나 기존 멀티페이지 앱에서 JavaScript를 한 줄도 건드리지 않아도 됩니다.
/* 모든 페이지의 <head> 또는 전역 CSS에 추가 */
@view-transition {
navigation: auto;
}
/* 기본 크로스페이드 대신 슬라이드 전환으로 변경 */
@keyframes slide-in-from-right {
from { transform: translateX(30px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slide-out-to-left {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(-30px); opacity: 0; }
}
::view-transition-old(root) {
animation: 300ms ease-in-out slide-out-to-left;
}
::view-transition-new(root) {
animation: 300ms ease-in-out slide-in-from-right;
}| 구성 요소 | 설명 |
|---|---|
@view-transition { navigation: auto; } |
동일 오리진 페이지 이동 시 전환 자동 활성화 |
::view-transition-old(root) |
떠나는 페이지(이전 스냅샷)의 애니메이션 |
::view-transition-new(root) |
들어오는 페이지(새 DOM)의 애니메이션 |
root |
전체 페이지 대상. 특정 요소 이름으로 교체 가능 |
Firefox 사용자에게는 애니메이션 없이 일반 페이지 이동이 발생합니다. 기능 자체는 정상 동작하므로 점진적 향상(Progressive Enhancement) 관점에서 문제없습니다.
프레임워크별 통합 패턴
가장 많이 쓰게 되는 패턴들을 모아뒀습니다. 프레임워크마다 통합 방식이 꽤 다릅니다.
Astro: 레이아웃 컴포넌트에 <ViewTransitions />를 한 번 선언하면 끝납니다. MPA 구조를 유지하면서 SPA 같은 전환을 얻는 가장 자연스러운 통합입니다.
---
import { ViewTransitions } from 'astro:transitions';
---
<html>
<head>
<ViewTransitions />
</head>
<body>
<slot />
</body>
</html>Next.js (App Router): next.config.js에 플래그를 추가하면 <Link> 클릭 시 자동으로 startViewTransition이 트리거됩니다. Next.js 15+에서 지원됩니다. React 19.2의 <ViewTransition> 컴포넌트와 함께 쓰면 더 세밀한 제어가 가능합니다.
// next.config.js (Next.js 15+)
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
viewTransition: true,
},
};
export default nextConfig;<ViewTransition> 컴포넌트의 핵심은 목록과 상세 양쪽에서 같은 name을 써야 공유 요소 전환이 연결된다는 점입니다.
// 목록 페이지
import { ViewTransition } from 'react';
function ProductCard({ product }) {
return (
<ViewTransition name={`product-${product.id}`}>
<img src={product.image} alt={product.name} />
</ViewTransition>
);
}
// 상세 페이지 — name이 같아야 두 요소가 하나의 전환으로 연결됩니다
function ProductDetail({ product }) {
return (
<ViewTransition name={`product-${product.id}`}>
<img src={product.image} alt={product.name} className="hero" />
</ViewTransition>
);
}SvelteKit: 공식 내장은 없지만, onNavigate 훅을 활용하는 패턴이 커뮤니티에서 잘 정착돼 있습니다. Promise를 반환하는 이유는 브라우저에게 전환 완료 시점을 명시적으로 알려주기 위해서입니다. 이 구조가 없으면 SvelteKit이 내비게이션을 언제 마무리해야 할지 알 수 없습니다.
// +layout.svelte 또는 +layout.js
import { onNavigate } from '$app/navigation';
onNavigate((navigation) => {
// 미지원 브라우저에서는 그냥 통과
if (!document.startViewTransition) return;
// Promise를 반환해 브라우저가 전환 완료 시점을 알 수 있게 합니다
return new Promise((resolve) => {
document.startViewTransition(async () => {
resolve();
await navigation.complete;
});
});
});접근성을 고려한 모션 처리
이게 빠지면 실무에서 꼭 한 번은 지적받습니다. prefers-reduced-motion을 브라우저가 자동으로 처리해주지 않아서 개발자가 직접 넣어야 합니다. 전정 장애나 모션 민감증이 있는 사용자에게 불편을 줄 수 있고, 접근성 감사에서도 자주 지적받는 항목입니다.
/* 전역 CSS에 처음부터 넣어두는 것을 권장합니다 */
@media (prefers-reduced-motion: reduce) {
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 라이브러리 의존성 제거 | Framer Motion, GSAP 없이 네이티브 CSS 애니메이션만으로 동작 |
| 컴포지터 레이어 처리 | transform·opacity 전환 구간이 GPU에서 처리되어 부드러운 애니메이션 |
| 점진적 향상 | 미지원 브라우저는 전환 없이 정상 동작. document.startViewTransition 존재 여부로 분기 가능 |
| MPA 적용 가능 | @view-transition at-rule로 클라이언트 라우터 없이 페이지 전환 애니메이션 적용 |
| 레이아웃 시프트 없음 | DOM 변경 전에 이미 스냅샷이 찍혀 있어 전환 중 레이아웃이 흔들리지 않음 |
| Baseline 달성 | 2025년 10월부터 4대 브라우저 최신 버전 모두 지원 (same-document 기준) |
이 중에서 실제로 가장 체감이 큰 건 MPA 적용 가능성입니다. 클라이언트 라우터를 붙이지 않아도 된다는 게, Astro나 기존 서버 렌더 앱 프로젝트에서는 상당히 큰 차이를 만들어냅니다.
컴포지터 레이어(Compositor Layer): 브라우저 렌더링 파이프라인에서 GPU가 직접 다루는 레이어입니다.
transform,opacity같은 속성이 이 레이어에서 처리되면 CPU 레이아웃 계산을 건드리지 않아 훨씬 부드럽게 동작합니다. View Transitions API는 스냅샷 전환 시 이 속성들을 기본으로 활용하기 때문에 성능 이점이 있습니다.
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 동시 전환 불가 | 문서당 하나의 전환만 실행 가능. 전환 중 새 전환 발생 시 이전 것이 건너뜀 | 전환 완료 후 다음 전환을 트리거하는 큐 구조 고려 |
view-transition-name 중복 |
동일 화면에 같은 이름이 두 개 이상이면 전환 전체 스킵 | 동적 리스트에는 ID 기반 고유 이름 부여 (Level 2에서 개선 예정) |
| 콜백 블로킹 | 콜백이 resolve되기 전까지 사용자에게 이전 페이지가 멈춘 채로 보임 | 콜백 안에서 무거운 데이터 페치 금지. 데이터 먼저 로드 후 전환 시작 권장 |
| 접근성 이슈 | 전환 중 구/신 DOM 공존 시 스크린 리더 중복 발표, 포커스 혼동 가능 | ARIA live region 관리, 전환 후 포커스 명시적 이동 처리 |
prefers-reduced-motion 미자동 처리 |
브라우저가 모션 감소 설정을 자동 반영하지 않음 | CSS에 @media (prefers-reduced-motion: reduce) 직접 작성 필수 |
| Firefox cross-document 미지원 | MPA에서 @view-transition 사용 시 Firefox는 일반 전환 |
점진적 향상으로 대응. Interop 2026 논의 중 |
실무에서 가장 흔한 실수
-
동적 리스트에서
view-transition-name중복:product-1,product-2처럼 ID 기반으로 이름을 생성하지 않으면, 같은 이름이 두 개 이상 등장하는 순간 전환 전체가 조용히 건너뜁니다. 처음에는 왜 안 되는지 한참 헤맬 수 있는 부분입니다. -
콜백 안에서
fetch호출: 데이터를 받아오는 동안 이전 페이지가 멈춘 채 사용자에게 그대로 노출됩니다. 데이터 로딩을 먼저 완료한 다음에 전환을 시작하는 순서로 바꾸는 게 좋습니다. -
prefers-reduced-motion처리 누락: 처음부터 전역 CSS에 넣어두는 습관을 들여두면 나중에 따로 챙길 필요가 없습니다. 접근성 감사에서 자주 지적받는 항목이기도 합니다.
마치며
View Transitions API는 이제 "실험적 기능"이 아니라, 라이브러리 없이 프로덕션에 쓸 수 있는 검증된 브라우저 네이티브 도구입니다. 그렇다면 지금 어디서부터 시작할 수 있을까요?
-
MPA 프로젝트가 있다면: 전역 CSS에
@view-transition { navigation: auto; }두 줄을 추가해보시면 됩니다. 별도의 JavaScript 없이 모든 페이지 이동에 기본 크로스페이드가 적용되는 걸 바로 확인할 수 있습니다. -
SPA 또는 프레임워크 프로젝트라면: Next.js는
next.config.js에experimental: { viewTransition: true }한 줄, Astro는 레이아웃에<ViewTransitions />컴포넌트를 추가하는 것부터 시작해보시면 좋습니다. SvelteKit이라면 위의onNavigate패턴을+layout.svelte에 붙여넣어보시면 됩니다. -
가장 인상적인 효과를 원한다면: 상품 목록 → 상세 화면처럼 반복되는 카드 UI가 있다면, 카드 이미지와 상세 페이지 히어로 이미지에 동일한
view-transition-name을 부여하는 것을 권장합니다. 처음 동작하는 순간의 놀라움은 직접 경험해보시면 압니다.
참고 자료
- View Transition API - MDN Web Docs — API 기본 레퍼런스, 처음 접할 때 출발점
- Using the View Transition API - MDN — 단계별 구현 방법 상세 설명
- What's new in view transitions (2025 update) - Chrome for Developers — Chrome 팀의 2025년 변경사항 정리
- Smooth transitions with the View Transition API - Chrome for Developers — 공식 구현 안내, 예시 풍부
- Misconceptions about view transitions - Chrome for Developers — 흔한 오해 정리, 팀 내 설득 시 유용
- CSS View Transitions Module Level 2 - W3C Draft — Level 2 사양 초안, 확정 전 문법 확인용
- React Labs: View Transitions, Activity, and more - React 공식 블로그 — React
<ViewTransition>발표 원문 - React
<ViewTransition>공식 문서 — 컴포넌트 API 레퍼런스 - next.config.js: viewTransition - Next.js 공식 문서 — Next.js 15+ 연동 설정
- View transitions - Astro 공식 문서 —
persist,animate지시어 포함 Astro 심화 패턴 - Animating Page and View Transitions with Accessibility in Mind — 접근성 처리 심화, ARIA 및 포커스 관리 포함
- Can I use: View Transitions API — 현재 브라우저 지원 현황 실시간 확인