GODRICH

CSS 페이지 전환 애니메이션 9종 — JS 없이, CSS만으로

CSS 페이지 전환 애니메이션은 한 화면이 사라지고 다음 화면이 나타나는 순간을 매끄럽게 잇는 CSS 동작이다. 아홉 개 중 세 개는 흔한 라이브러리 템플릿에서 잘 안 보이는 조합이다.

자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip

아홉 개는 발견되는 순서가 아니라 화면이 실제로 바뀌는 상황 순서로 놓았다. 01~02는 어떤 페이지에도 쓰는 가장 기본적인 크로스페이드·슬라이드이고, 03~06은 클릭한 요소와 다음 화면을 연결하는 전환(공유 요소·원형 확산·스택 푸시·카드 확대)이다. 07~08은 페이지 전체가 아니라 화면 안 콘텐츠 일부만 바뀌는 전환(탭·목록 순서)이고, 09는 화면 전체의 테마가 바뀌는 가장 큰 전환이라 맨 뒤에 뒀다.

01기본 크로스페이드

이전 페이지가 옅어지며 사라지고 다음 페이지가 같은 자리에 겹쳐서 나타난다. 내비게이션 사이 전환 대부분의 기본값으로 쓴다.

300ms느리게 500ms살짝 확대
.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좌우 슬라이드

다음 페이지가 오른쪽에서 밀고 들어오고 이전 페이지는 왼쪽으로 밀려난다. 단계형 폼처럼 순서가 있는 화면 사이에 쓴다.

방향 반전250ms그림자 추가
.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을 직접 바꾸는 대신 같은 요소 하나의 크기만 옮긴다.

view-transition-name400ms라운드 유지
.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로 넘긴다.

clip-path circle600ms중앙 고정
.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앱 스택 푸시 전환

새 화면이 오른쪽에서 쌓이듯 밀려 들어오고, 뒤 화면은 살짝 줄어들며 어두워진다. 모바일 웹앱의 설정 화면 이동에 쓴다.

280ms뒤 화면 어둡게엣지 스와이프
.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카드→상세 확대 전환

클릭한 카드가 화면 전체 크기로 확대되며 상세 페이지가 된다. 갤러리·뉴스 카드 목록에 쓴다.

350ms모서리 반경 0으로배경 어둡게
.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 위에서 겹쳐 내려온다. 설정·대시보드의 탭 콘텐츠에 쓴다.

200ms8px 오프셋깜빡임 없이
.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 하나로 옮긴다.

FLIP 기법300ms순위 뱃지 강조
.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 쐐기 하나로 레이아웃을 다시 계산하지 않는다.

대각선450ms가장자리부터
.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 APIcaniuse에서 최신 지원 현황을 확인할 수 있다.

두 번째는 겹친 두 페이지의 쌓임 순서다. 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를 쓰는 경우 중간에 반대 방향으로 줄어드는 모양이 부자연스러울 수 있어 빠른 연타에는 전환 시간을 더 짧게 두는 걸 권합니다.

압축 비밀번호를 입력하세요

비밀번호는 이 글 본문 안에 있습니다. 읽다 보면 나옵니다.