FEInterview Prep

Velog

CSS만으로 스크롤 기반 애니메이션 구현하기

스크롤 기반 애니메이션을 더 이상 JS로 손코딩할 필요가 없다. animation-timeline: scroll()/view() + @keyframes 두 줄로 컴포지터 스레드에서 돌아가는 애니메이션을 만들 수 있다.

2025-07-28·7분 읽기
CSS
원문 보기 ↗

핵심 요약

스크롤 기반 애니메이션은 타겟 / @keyframes / 타임라인 세 요소로 구성된다. 새로 들어온 타임라인 종류는 두 가지다.

타임라인동력대표 사용처
scroll()스크롤 컨테이너 전체 진행률페이지 진행 바, 가로 갤러리
view()특정 요소가 뷰포트를 가로지르는 비율페이드인, 패럴랙스, 등장 효과

핵심 사용 패턴은 다음과 같다.

@keyframes grow {
  from { width: 0; }
  to   { width: 100%; }
}
footer::after {
  animation: grow linear;
  animation-timeline: scroll(); /* 또는 view() */
}

브라우저 지원은 Chromium 계열이 먼저, Safari 26 베타가 합류했고 Firefox 는 플래그 단계. 따라서 실무에선 점진적 향상(progressive enhancement) 으로 적용해야 한다.

애니메이션의 "동력"을 시간이 아닌 스크롤로 갈아끼우는 것이 핵심이다. 기존 CSS 애니메이션은 문서 타임라인(시간) 위에서 진행되지만, animation-timeline 속성을 통해 동력을 스크롤 위치로 교체한다. @keyframes 자체는 그대로 — 바뀌는 건 "무엇이 진행률을 결정하는가" 뿐이다. 그래서 IntersectionObserver/스크롤 이벤트를 안 쓰고도 동일한 효과를 컴포지터 스레드에서 돌릴 수 있다.

스크롤 연동 애니메이션은 지금까지 JS 진영의 가장 무거운 영역이었다. 매 프레임 스크롤 위치를 읽어 transform을 갱신하면 메인 스레드가 막히고, IntersectionObserver는 "진입/이탈"만 알지 "진행률"은 모른다. 이번 표준은 다음 세 가지를 한 번에 해결한다.

  • 메인 스레드 비의존: transform/opacity 키프레임이면 GPU 합성으로 처리
  • 선언형 코드: 이벤트 콜백 없이 CSS 한 블록
  • 접근성 친화: prefers-reduced-motion 으로 즉시 끌 수 있음

면접에서 "스크롤 진행 바를 어떻게 구현하시겠습니까" 같은 질문이 오면, JS 답변과 CSS 답변을 둘 다 가지고 트레이드오프를 말할 수 있어야 한다.

학습 포인트

면접 답변으로 연결할 학습 포인트입니다.

`scroll()` vs `view()` 의 의미 차이

둘 다 "스크롤로 진행률을 만든다" 는 점은 같지만 기준점이 다르다.

  • scroll(): 가장 가까운 스크롤 컨테이너의 0% ~ 100% 를 진행률로 매핑. 페이지 전체 프로그레스 바에 적합.
  • view(): 대상 요소가 뷰포트를 통과하는 정도 를 진행률로 매핑. 카드가 뷰포트에 들어올 때 페이드인 같은 "요소 단위" 효과에 적합.
.progress { animation-timeline: scroll(root block); }
.card     { animation-timeline: view(); }
animation-timelinescroll()view()scroll container
자주 하는 오해

카드 등장 효과에 scroll() 을 쓰는 것. 카드 위치와 무관하게 페이지 전체 스크롤에 끌려가서 모든 카드가 동시에 변한다. 요소 단위 효과는 반드시 view().

JS 구현과의 성능/책임 분리

기존 JS 방식과 비교해 보면 어디가 이득이고 어디가 한계인지가 보인다.

방식스레드진행률 해상도한계
scroll 이벤트메인매우 높음입력 지연·jank
IntersectionObserver메인(콜백)이산값(threshold)부드러운 보간 불가
Scroll-Driven Animations컴포지터연속 0~1transform/opacity 외엔 효과 제한

단순 진행률·시각 효과는 CSS 가 압승, DOM 조작이나 데이터 로딩 같은 사이드 이펙트는 여전히 JS 쪽이 필요하다.

compositor threadIntersectionObserverjanktransform-only animation
자주 하는 오해

CSS 만으로 모든 스크롤 효과를 대체할 수 있다고 오해하는 것. 스크롤로 데이터 페칭 이나 상태 변경 은 여전히 JS 책임이다.

접근성과 점진적 향상

스크롤 애니메이션은 모션 민감 사용자에게 직접적인 위험이다. 다음 두 줄을 빼먹으면 회사 디자인 시스템 통과를 못 한다.

@media (prefers-reduced-motion: reduce) {
  .card { animation: none; }
}
@supports not (animation-timeline: scroll()) {
  /* IntersectionObserver 폴백 또는 정적 표시 */
}

Firefox·구버전 Safari 는 아직 미지원이므로 "애니메이션 없이도 콘텐츠가 정상 보여야 한다" 가 절대 원칙.

prefers-reduced-motion@supportsprogressive enhancement
자주 하는 오해

초기 상태(opacity: 0)로 두고 애니메이션이 진입할 때 보이게 만드는 패턴. 미지원 브라우저에서는 콘텐츠가 영원히 안 보인다.

읽는 순서

  1. 1이론

    animation-timeline 속성과 scroll()/view() 함수의 정의, 컴포지터 스레드 합성의 의미를 정리한다. "진행률의 동력이 시간 → 스크롤로 바뀐다" 라는 한 문장으로 요약해본다.

  2. 2구현

    footer::after 진행 바와 카드 등장 페이드인을 각각 scroll(), view() 로 구현해보고, 같은 효과를 IntersectionObserver 로도 만들어 코드량과 부드러움을 비교한다.

  3. 3실무

    프로젝트에서 스크롤 이벤트로 transform 을 갱신하는 곳을 찾아 transform/opacity 만 쓰는 효과인지 확인하고, 가능하면 CSS 로 치환 + @supports/prefers-reduced-motion 폴백을 함께 추가한다.

  4. 4설명

    동료에게 "CSS Scroll-Driven Animation 과 IntersectionObserver 중 무엇을 언제 쓰나" 를 5분 안에 트레이드오프 표 없이 말로 설명해본다. 막히면 브라우저 매트릭스 / 효과 종류 / 사이드 이펙트 세 축으로 정리.

면접 연결 질문

medium스크롤에 따라 헤더에 진행 바를 표시해야 합니다. JS 와 CSS Scroll-Driven Animation 중 어떤 걸 쓰시고, 그 이유는 뭔가요?
힌트

[감점 답변] "CSS 가 새 표준이니까 CSS". [좋은 답변] 시각 효과만 필요하면 CSS — 메인 스레드 비의존이라 jank 가 없고, 코드량도 압도적으로 작다. 다만 Firefox/구 Safari 지원 요구가 강하면 @supports 폴백 + IntersectionObserver/scroll 이벤트 조합. 즉 "브라우저 매트릭스 + 효과 종류(시각만 vs 데이터 변화)" 두 축으로 결정한다고 말한다.

medium`animation-timeline: scroll()` 과 `view()` 의 차이를 1분 안에 설명해주세요.
힌트

[감점 답변] "둘 다 스크롤에 따라 움직이는 거예요". [좋은 답변] 기준점으로 갈린다. scroll()스크롤 컨테이너의 전체 스크롤 진행률 을 0~1 로 매핑(페이지 진행 바). view()해당 요소가 뷰포트를 통과하는 비율 을 매핑(요소 등장 효과). 한 줄로: "페이지 단위면 scroll, 요소 단위면 view".

hardScroll-Driven Animation 을 도입했을 때 접근성 측면에서 반드시 지켜야 하는 두 가지를 말해보세요.
힌트

[감점 답변] "prefers-reduced-motion 만 쓰면 됨". [좋은 답변] 두 가지를 분리한다. (1) @media (prefers-reduced-motion: reduce)모션 비활성화 분기, (2) 미지원 브라우저에서도 콘텐츠가 보이도록 초기 상태를 정적으로 정상 노출 시키고, 애니메이션은 진행률에 따라 위에 덧붙는 방식으로 작성. 즉 "애니메이션 = 보너스" 원칙.

자기 점검

스크롤 진행률에 따라 색이 바뀌는 카드가 있다고 할 때, `scroll()` 로 짠 CSS 가 왜 의도와 다르게 동작했는지 진단 흐름을 설명해보세요.
animation-timelineview()scroll container기준점
자주 하는 오해

scroll() 을 쓰면 각 요소별 진행률이 자동으로 잡힌다고 오해하는 것. 실제로는 컨테이너 전체 진행률 이라 모든 카드가 같은 진행률을 공유한다.

IntersectionObserver 와 비교했을 때 Scroll-Driven Animation 으로는 못 하는 일을 두 가지 들어보세요.
사이드 이펙트데이터 페칭임계값메인 스레드
자주 하는 오해

"속도가 빠르니 모든 걸 대체할 수 있다" 라는 결론. 실제로는 DOM 조작·네트워크 트리거 등 사이드 이펙트 는 CSS 만으로 불가능.