블로그 목록
프론트엔드CSSUX

View Transitions API로 페이지 전환 애니메이션 만들기

페이지 전환 애니메이션을 구현할 때마다 Framer Motion을 쓰거나 CSS transition을 복잡하게 엮었다. 그 수고를 브라우저가 직접 해주는 API가 생겼다. View Transitions API다.

Chrome에 먼저 들어왔고, 지금은 Safari, Firefox도 지원한다. 모바일 Safari는 18버전부터 기본 지원이니까 이제 실제 프로젝트에 쓸 수 있는 타이밍이 됐다.

기본 사용법

document.startViewTransition(() => {
  updateTheDOMSomehow();
});

startViewTransition에 DOM 변경 로직을 넘기면, 브라우저가 전환 전/후 스냅샷을 찍고 크로스페이드 애니메이션을 붙여준다. 기본은 크로스페이드지만 CSS로 커스터마이징할 수 있다.

CSS로 애니메이션 제어하기

전환 중에 브라우저가 ::view-transition-old, ::view-transition-new 슈도 엘리먼트를 만든다.

@keyframes slide-in {
  from {
    transform: translateX(100%);
  }
  to {
    transform: translateX(0);
  }
}

@keyframes slide-out {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-100%);
  }
}

::view-transition-old(root) {
  animation: 300ms ease-out slide-out;
}

::view-transition-new(root) {
  animation: 300ms ease-out slide-in;
}

특정 요소만 전환 지정하기

페이지 전체가 아니라 특정 요소만 부드럽게 전환하고 싶을 때 view-transition-name을 쓴다.

.hero-image {
  view-transition-name: hero;
}

같은 이름을 전환 전/후 두 DOM에 모두 붙여두면, 브라우저가 위치와 크기를 보간해서 FLIP 애니메이션처럼 동작한다. 카드 목록 → 상세 페이지 전환에 쓰면 요소가 자연스럽게 이어지는 느낌을 준다.

이름은 페이지 내에서 유일해야 한다. 중복되면 전환이 동작하지 않는다. 동적으로 여러 개를 쓸 때는 style 속성에 인라인으로 넣는 방법을 쓴다.

Next.js에서 쓰기

App Router 기준으로 useRouterpush를 래핑하면 된다.

import { useRouter } from 'next/navigation';

export function useViewTransitionRouter() {
  const router = useRouter();

  function push(href: string) {
    if (!document.startViewTransition) {
      router.push(href);
      return;
    }

    document.startViewTransition(() => {
      router.push(href);
    });
  }

  return { push };
}

document.startViewTransition이 없는 브라우저를 위한 폴백을 넣어두는 게 좋다. 있으면 쓰고 없으면 그냥 이동하는 패턴이라 progressive enhancement로 깔끔하게 쓸 수 있다.

브라우저 지원

Chrome, Edge는 111부터, Safari는 18.2부터 지원한다. Firefox는 아직 플래그 뒤에 있다. 지원하지 않는 브라우저에서는 전환 효과 없이 그냥 동작하면 되니까, 체크만 잘 넣으면 점진적으로 개선되는 UX를 거의 공짜로 얻는 셈이다.

prefers-reduced-motion 대응

접근성 설정에서 애니메이션을 끈 사용자에게 전환 효과가 불편할 수 있다.

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-image-pair(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

라이브러리 없이 브라우저 기본 기능으로 이 정도 품질의 전환 애니메이션을 만들 수 있게 됐다. 특히 view-transition-name으로 공유 요소 전환을 구현하면, 예전엔 꽤 복잡한 작업이었던 게 CSS 한 줄로 끝난다. Framer Motion처럼 정교한 물리 기반 애니메이션은 아니지만, 화면 전환이나 요소 이동 같은 기본적인 UX 개선에는 충분하다.