CSS 페이지 전환 애니메이션 9종 — JS 없이, CSS만으로
CSS 페이지 전환 애니메이션은 한 화면이 사라지고 다음 화면이 나타나는 순간을 매끄럽게 잇는 CSS 동작이다. 아홉 개 중 세 개는 흔한 라이브러리 템플릿에서 잘 안 보이는 조합이다.
자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip
- 01 기본 크로스페이드
- 02 좌우 슬라이드
- 03 썸네일→히어로 공유 요소
- 04 클릭 지점 원형 확산
- 05 앱 스택 푸시 전환
- 06 카드→상세 확대 전환
- 07 탭 콘텐츠 크로스페이드
- 08 정렬 시 항목 이동 애니메이션
- 09 다크모드 와이프 전환
아홉 개는 발견되는 순서가 아니라 화면이 실제로 바뀌는 상황 순서로 놓았다. 01~02는 어떤 페이지에도 쓰는 가장 기본적인 크로스페이드·슬라이드이고, 03~06은 클릭한 요소와 다음 화면을 연결하는 전환(공유 요소·원형 확산·스택 푸시·카드 확대)이다. 07~08은 페이지 전체가 아니라 화면 안 콘텐츠 일부만 바뀌는 전환(탭·목록 순서)이고, 09는 화면 전체의 테마가 바뀌는 가장 큰 전환이라 맨 뒤에 뒀다.
01기본 크로스페이드
이전 페이지가 옅어지며 사라지고 다음 페이지가 같은 자리에 겹쳐서 나타난다. 내비게이션 사이 전환 대부분의 기본값으로 쓴다.
.page {
position: absolute;
inset: 0;
transition: opacity $duration $easing;
}
.page--a { opacity: 1; }
.page--b { opacity: 0; }
[data-active="b"] .page--a { opacity: 0; }
[data-active="b"] .page--b { opacity: 1; }
02좌우 슬라이드
다음 페이지가 오른쪽에서 밀고 들어오고 이전 페이지는 왼쪽으로 밀려난다. 단계형 폼처럼 순서가 있는 화면 사이에 쓴다.
.page {
position: absolute;
inset: 0;
transition: transform $duration $easing;
}
.page--a { transform: translateX(0); }
.page--b { transform: translateX(100%); }
[data-active="b"] .page--a { transform: translateX(-100%); }
[data-active="b"] .page--b { transform: translateX(0); }
03썸네일→히어로 공유 요소
목록의 썸네일 하나가 그대로 늘어나며 다음 페이지의 큰 이미지로 이어진다. width·height·top을 직접 바꾸는 대신 같은 요소 하나의 크기만 옮긴다.
.hero {
position: absolute;
left: 18px; top: 60px;
width: 30%; height: 44px;
transform-origin: top left;
transition: width $duration $easing, height $duration $easing, top $duration $easing;
}
[data-active="hero"] .hero {
width: 88%; height: 62%; top: 14px;
}
04클릭 지점 원형 확산
클릭한 지점에서 원이 퍼지며 다음 페이지를 덮는다. 클릭 좌표는 JS가 CSS 변수 --x --y로 넘긴다.
.page--b {
clip-path: circle(0% at var(--x) var(--y));
transition: clip-path $duration $easing;
}
[data-active="b"] .page--b {
clip-path: circle(150% at var(--x) var(--y));
}
05앱 스택 푸시 전환
새 화면이 오른쪽에서 쌓이듯 밀려 들어오고, 뒤 화면은 살짝 줄어들며 어두워진다. 모바일 웹앱의 설정 화면 이동에 쓴다.
.root { transition: transform $duration $easing; }
.pushed {
position: absolute; inset: 0;
transform: translateX(100%);
transition: transform $duration $easing;
}
.dim { opacity: 0; transition: opacity $duration $easing; }
[data-active="pushed"] .root { transform: scale(.94); }
[data-active="pushed"] .pushed { transform: translateX(0); }
[data-active="pushed"] .dim { opacity: .18; }
06카드→상세 확대 전환
클릭한 카드가 화면 전체 크기로 확대되며 상세 페이지가 된다. 갤러리·뉴스 카드 목록에 쓴다.
.cell-focus { transition: transform $duration $easing; }
.detail {
position: absolute; inset: 0;
opacity: 0;
transition: opacity $duration $easing;
}
[data-active="detail"] .cell-focus { transform: scale(4); }
[data-active="detail"] .detail { opacity: 1; }
07탭 콘텐츠 크로스페이드
탭을 바꾸면 이전 콘텐츠는 사라지고 새 콘텐츠가 8px 위에서 겹쳐 내려온다. 설정·대시보드의 탭 콘텐츠에 쓴다.
.panel {
transition: opacity $duration $easing, transform $duration $easing;
}
.panel--a { opacity: 1; transform: translateY(0); }
.panel--b { opacity: 0; transform: translateY(8px); }
[data-active="b"] .panel--a { opacity: 0; transform: translateY(-8px); }
[data-active="b"] .panel--b { opacity: 1; transform: translateY(0); }
08정렬 시 항목 이동 애니메이션
정렬 기준을 바꾸면 순위가 바뀐 항목만 이동한 거리만큼 부드럽게 움직인다. FLIP 기법으로 이동 거리를 미리 계산해 transform 하나로 옮긴다.
.row {
transition: transform $duration $easing;
}
.row-1 { top: 0; }
.row-2 { top: 34px; }
[data-active="swapped"] .row-1 { transform: translateY(34px); }
[data-active="swapped"] .row-2 { transform: translateY(-34px); }
09다크모드 와이프 전환
화면 모서리에서 어두운 색이 대각선으로 번지며 다크모드로 바뀐다. clip-path 쐐기 하나로 레이아웃을 다시 계산하지 않는다.
.dark {
clip-path: polygon(0 0, 0 0, 0 0);
transition: clip-path $duration $easing;
}
[data-active="dark"] .dark {
clip-path: polygon(0 0, 200% 0, 0 200%);
}
어디서 깨지는가 — 함정 셋
첫 번째는 브라우저 차이다. 위 아홉 개는 전부 클래스·data 속성 토글과 transition·clip-path만으로 동작해 어떤 최신 브라우저에서도 같게 움직인다. 다만 실제 서비스에서 더 매끄러운 전환을 원하면 Chrome·Edge가 지원하는 View Transitions API로 옮길 수 있는데, Safari·Firefox는 이 글을 쓰는 시점까지 지원이 갈린다. MDN View Transitions API와 caniuse에서 최신 지원 현황을 확인할 수 있다.
두 번째는 겹친 두 페이지의 쌓임 순서다. 04 원형 확산과 09 다크모드 와이프는 뒤 페이지가 이미 DOM에 있고 앞 페이지가 clip-path로 걷히는 방식이라, z-index를 안 정해두면 브라우저마다 어느 페이지가 위에 그려질지 달라진다. 이 zip의 아홉 개는 전부 나중에 오는 요소가 위로 오는 문서 순서 그대로 두고 z-index를 새로 주지 않았다.
세 번째는 480×300 무대에서 실측한 결과다. 03 공유 요소 전환은 썸네일이 두 프레임 만에 히어로 크기까지 벌어져 중간 단계가 안 보인 적이 있어 확산 구간을 40~90%로 늘렸고, 그렇게 다듬은 아홉 개 파일의 압축 비밀번호는 amqzkrw2이다.
접근성
시스템에서 동작 줄이기를 켠 사용자에게는 아홉 개 전부 transition을 끄고 전환 뒤의 최종 상태만 바로 보여준다. 01·02·07은 다음 페이지를, 03·06은 확대된 상세를, 04·09는 덮인 다크 화면을, 05는 쌓인 새 화면을, 08은 바뀐 순서를 그대로 고정한다. 세부는 MDN prefers-reduced-motion에서 볼 수 있다.
이 자리에서 다른 CSS 전환 애니메이션도 볼 수 있는 카테고리 페이지와, 사이트 소개를 담은 소개 페이지로 이어진다.
FAQ
페이지 전환에 View Transitions API를 꼭 써야 하나요?
그렇지 않습니다. 위 아홉 개는 View Transitions API 없이 클래스·data 속성 토글과 CSS transition·clip-path만으로 동작합니다. API는 브라우저가 전환 스냅샷을 대신 찍어주는 지름길일 뿐이고, 없어도 같은 결과를 손으로 만들 수 있습니다.
React 라우터가 바뀔 때 이 컴포넌트를 어떻게 트리거하나요?
react/ 폴더의 아홉 개 컴포넌트는 모두 active props 하나로 어느 화면이 보이는지 정합니다. 라우터의 현재 경로를 이 props에 그대로 연결하면 경로가 바뀔 때마다 transition을 자동으로 실행합니다.
전환 중에 뒤로가기를 누르면 애니메이션이 끊기나요?
끊길 수 있습니다. 이 zip의 데모는 전환이 끝나기 전에 active 값이 다시 바뀌면 CSS transition이 새 목표값을 향해 그 자리에서 방향을 바꾸므로 완전히 끊기지는 않지만, 04·09처럼 clip-path를 쓰는 경우 중간에 반대 방향으로 줄어드는 모양이 부자연스러울 수 있어 빠른 연타에는 전환 시간을 더 짧게 두는 걸 권합니다.