스크롤을 따라 천천히 떠오르는 카드, 위쪽에 가늘게 차오르는 진행 바, 화면을 가로지르는 패럴랙스 이미지. 사이트마다 자연스럽게 자리잡은 이 효과들 뒤에는 거의 빠짐없이 GSAP, ScrollMagic, AOS 같은 자바스크립트 라이브러리가 한두 개씩 들어 있다. 번들은 무거워지고, 메인 스레드는 스크롤 이벤트로 가득 차고, 결국 첫 화면이 늦게 그려진다. Scroll-driven Animations는 이 구조를 통째로 바꾼다. CSS의 animation-timeline 한 줄로 스크롤 위치 자체를 애니메이션의 시간축으로 사용하는 새로운 표준이다.
스크롤 이벤트를 듣지 않고 스크롤에 맞춰 움직인다
기존 방식은 단순했지만 비쌌다. 자바스크립트가 scroll 이벤트를 듣고, 매 프레임마다 요소의 위치와 진행률을 계산해, 클래스나 인라인 스타일로 애니메이션을 트리거한다. 라이브러리는 이 계산을 추상화해 줄 뿐, 메인 스레드를 점유하는 본질은 같다.
Scroll-driven Animations는 진행률 계산을 브라우저의 컴포지터가 직접 한다. 자바스크립트가 개입하지 않으니 메인 스레드가 비고, 60fps가 흔들리지 않는다. 사이트가 가벼워지는 것은 덤이다. 라이브러리 하나가 줄어들 때마다 첫 화면(LCP)이 빨라지고, 모바일에서의 체감 성능 차이는 더 커진다.
scroll()과 view() — 두 가지 타임라인부터 구분한다
Scroll-driven Animations의 핵심은 두 가지 타임라인이다. 이름만 정확히 익혀도 절반은 끝난 셈이다.
- scroll() 타임라인: 페이지 전체 또는 특정 스크롤 컨테이너의 진행률(0%~100%)을 시간축으로 사용한다. 페이지 상단에 가늘게 차오르는 읽기 진행 바, 사이드의 챕터 인디케이터 같은 전체 진행률 기반 효과에 쓴다.
- view() 타임라인: 특정 요소가 뷰포트에 들어와서 빠져나갈 때까지의 진행률을 시간축으로 사용한다. 카드가 화면에 들어올 때 페이드인되거나, 이미지가 옆에서 슬라이드해 들어오는 요소 단위 효과에 쓴다.
지금까지 자바스크립트로 IntersectionObserver를 만들어 처리하던 일의 90%는 view()로 옮길 수 있다.
첫 적용은 '진행 바'와 '페이드인' 두 가지로 충분하다
모든 효과를 한 번에 바꾸려고 하면 도입이 늦어진다. 가장 효과가 크고 코드가 짧은 두 패턴부터 적용하는 것을 권한다.
1) 페이지 상단 읽기 진행 바
.progress { position: fixed; top: 0; left: 0; height: 3px; width: 100%; transform-origin: left; animation: grow linear; animation-timeline: scroll(root block); }
@keyframes grow { from { transform: scaleX(0); } to { transform: scaleX(1); } }
2) 카드가 뷰포트에 들어올 때 페이드인
.card { animation: fade-in linear both; animation-timeline: view(); animation-range: entry 10% cover 30%; }
@keyframes fade-in { from { opacity: 0; transform: translateY(24px); } to { opacity: 1; transform: translateY(0); } }
둘 다 자바스크립트는 단 한 줄도 필요 없다. animation-range로 '어느 구간에서 시작해 어느 구간에서 끝낼지'를 다듬으면 어색하게 끊기는 느낌도 사라진다.
지원 브라우저와 폴백 — 콘텐츠는 언제나 보여야 한다
Chrome과 Edge는 이미 안정 지원하고 있고, Firefox와 Safari는 구현이 진행 중이다. 작은 사이트라면 지금 도입해도 좋지만, 폴백을 잘못 짜면 미지원 브라우저에서 콘텐츠가 안 보이는 사고가 난다. 두 가지 원칙을 지키면 안전하다.
- 기본 상태는 항상 '보이는' 상태로 둔다. 애니메이션은 보이는 상태를 '꾸미는' 역할만 하게 한다. 예를 들어 페이드인 카드는 기본 opacity가 1이어야 한다.
- @supports (animation-timeline: scroll()) 안에서만 시작 상태(opacity: 0 등)를 만든다. 이렇게 하면 미지원 브라우저는 모든 카드를 처음부터 그대로 보여준다.
그리고 잊지 말아야 할 한 가지, prefers-reduced-motion이다. 어지러움에 민감한 사용자는 OS 설정에서 모션 최소화를 켜둔다. @media (prefers-reduced-motion: reduce) 안에서 animation-timeline을 해제하는 것은 선택이 아니라 의무에 가깝다.
작은 사이트일수록 효과가 크다
대형 사이트는 이미 자체 애니메이션 엔진이 있다. 정작 효과가 큰 것은 라이브러리 한두 개에 의존하던 작은 회사 사이트 쪽이다. AOS 하나만 빼도 자바스크립트 번들이 20KB 가까이 줄고, 초기 파싱 시간이 함께 줄어든다. 같은 효과를 더 부드럽게, 더 적은 코드로 얻을 수 있다면 굳이 미룰 이유가 없다.
CYAN에서 새로 만드는 사이트와 리뉴얼하는 사이트에는 이 패턴을 표준으로 적용하고 있다. 진행 바와 페이드인은 폴백까지 갖춰 기본 템플릿에 들어가고, 패럴랙스가 필요한 페이지만 view() 타임라인으로 추가한다. 사장님 입장에서는 라이브러리 의존성이 하나 줄어든다는 것이 곧 운영비 한 줄이 줄어든다는 뜻이기도 하다. 다음에 리뉴얼을 고민하실 때, 지금 사이트에 몇 개의 스크롤 라이브러리가 실려 있는지 한 번 확인해 보시기를 권한다.