스크롤에 맞춰 움직이는 효과는 이제 자바스크립트 없이 CSS animation-timeline: view()/scroll()로 만듭니다. 메인 스레드를 덜 막아 더 부드럽고, 코드도 짧습니다. 단, 아직 모든 브라우저에서 되지는 않으므로 @supports 폴백을 반드시 두고, 핵심 요소에만 절제해서 씁니다.
요약
- 스크롤 연동 애니메이션 = 예전 JS 리스너·IntersectionObserver → 이제 CSS
animation-timeline. - 성능 이점: 브라우저가 컴포지터에서 처리해 메인 스레드를 덜 막는다 → 스크롤이 부드럽다.
- 아직 Baseline(보편 지원)이 아니다 →
@supports로 미지원 브라우저는 정상 상태로 폴백. - 과용 금지 — 핵심 요소에만,
prefers-reduced-motion사용자에겐 끈다.
몇 년 전까지 “이 박스가 화면에 들어오면 스르륵 나타나게 해주세요”라는 요청은 의외로 비쌌습니다. 스크롤 위치를 자바스크립트로 계속 읽거나, 요소마다 IntersectionObserver를 붙이고, 클래스를 토글하고, 모바일에서 끊기지 않게 디바운스까지 챙겨야 했죠. 효과 하나에 코드 수십 줄. 그리고 그 코드는 전부 메인 스레드에서 돌았습니다. 사용자가 빠르게 스크롤하면, 그 계산과 화면 그리기가 같은 줄에 서서 서로를 기다리다 끊겼습니다.
그래서 지금은 어떻게 하냐고요?
CSS 한 줄로 바뀌었습니다. animation-timeline: view() — “이 요소가 화면에 보이는 진행도”를 그대로 애니메이션의 타임라인으로 씁니다. 시간이 아니라 스크롤이 재생 헤드가 되는 거죠. 말보다 빠릅니다. 아래 바를 보세요. 이 블록을 지나며 당신의 스크롤에 맞춰 채워집니다.
스크롤에 맞춰 채워지는 바 (JS 없이)
CSS animation-timeline:view()로 만든 진행 바입니다. 이 블록을 지나며 채워집니다.
// 순수 CSS scroll-driven animation · 미지원 브라우저는 채워진 상태로 폴백
JS 리스너로 하던 걸, 왜 굳이 CSS로 옮길까요?
가장 큰 이유는 성능입니다. 자바스크립트 스크롤 리스너는 사용자가 스크롤할 때마다 메인 스레드에서 코드를 실행합니다. 그 스레드는 동시에 화면도 그려야 하죠. 둘이 겹치면 프레임이 밀립니다. CSS scroll-driven animation은 브라우저가 이걸 컴포지터 쪽에서 처리할 수 있어서, 메인 스레드가 바쁜 와중에도 스크롤 애니메이션은 부드럽게 흐릅니다. 게다가 코드가 짧아집니다 — 수십 줄짜리 옵저버 로직이 CSS 두세 줄로 줄어요.
view()와 scroll(), 뭘 언제 써야 하나요?
둘은 “무엇을 타임라인으로 삼느냐”가 다릅니다. view()는 그 요소 하나가 화면에 보이는 동안을 진행도로 씁니다 — 들어올 때 페이드인, 나갈 때 페이드아웃 같은 등장 효과에 맞습니다. scroll()은 스크롤 컨테이너 전체의 진행을 씁니다 — 페이지 맨 위의 ‘읽기 진행 바’처럼 문서 전체에 걸친 표시에 맞고요. 위 데모는 “이 블록이 보이는 구간”에 맞춰야 자연스러우니 view()를 썼습니다.
저는 위 진행 바를 만들 때 실제로 이만큼만 적었습니다 — JS 한 줄 없이, @supports 폴백까지 포함해서요. 그대로 복사해 등장 효과에 바로 쓰셔도 됩니다.
.bar{ transform: scaleX(0); transform-origin: left; }
@supports (animation-timeline: view()){
.bar{
animation: fill linear both;
animation-timeline: view(); /* 이 요소가 보이는 동안을 타임라인으로 */
animation-range: cover 12% cover 85%;
}
@keyframes fill{ to{ transform: scaleX(1); } }
}
@media (prefers-reduced-motion: reduce){
.bar{ transform: scaleX(1); animation: none; } /* 멀미 배려 */
}핵심은 animation-timeline 한 줄입니다. 시간이 아니라 스크롤이 재생 헤드가 되고, 미지원 브라우저는 @supports 바깥의 폴백 상태(채워짐)로 남습니다.
그럼 이제 자바스크립트는 필요 없나요?
시각 효과만 보면 많이 줄어듭니다. 하지만 “스크롤이 여기 닿으면 데이터를 불러와라”, “조건에 따라 다른 콘텐츠를 보여라” 같은 로직은 여전히 JS의 일입니다. 저는 이렇게 나눕니다 — 보이는 움직임은 CSS, 판단과 데이터는 JS. 그러면 애니메이션이 로직에 발목 잡히지 않고, 로직도 깔끔해집니다.
모든 브라우저에서 되나요? (솔직한 부분)
아직 전부는 아닙니다. animation-timeline은 일부 브라우저에서만 동작하는 ‘제한적 사용’ 단계라, 글 쓰는 지금 기준으로 아직 Baseline(보편 지원)이 아닙니다. 그래서 실무에서는 폴백이 필수입니다. 저는 효과를 @supports (animation-timeline: view())로 감싸서, 지원하는 브라우저에서만 애니메이션이 켜지고 미지원 브라우저는 그냥 완성된(채워진) 상태로 보이게 합니다. 효과가 안 보일 뿐, 콘텐츠는 멀쩡합니다. 참고로 2026년에는 특정 스크롤 지점을 ‘넘는 순간’ 한 번 재생되는 스크롤 트리거 애니메이션이 크롬에 추가되는 등 이 분야는 계속 넓어지고 있습니다. 지원 범위는 매달 바뀌니, 적용 시점에 다시 확인하는 게 원칙입니다.
좋다고 막 쓰면 안 되는 이유
CSS로 쉬워졌다고 모든 요소를 스크롤에 묶으면, 화면이 사방에서 움직여 산만하고 멀미를 유발합니다. 저는 한 화면에 스크롤 연동 효과는 한두 개로 제한합니다. 그리고 멀미·전정기관 민감성이 있는 분을 위해 prefers-reduced-motion: reduce를 켠 사용자에게는 애니메이션을 꺼서 정적인 상태로 보여줍니다. 위 데모도 그렇게 만들었습니다. 멋과 배려는 같이 갑니다.
| 항목 | JS 스크롤 리스너 | CSS scroll-timeline |
|---|---|---|
| 성능(스크롤 중 끊김) | 메인 스레드 점유 → jank 위험 | 컴포지터 처리 가능 → 부드러움 |
| 코드량 | 옵저버·핸들러·디바운스 수십 줄 | CSS 두세 줄 |
| 부드러움 | 프레임 밀림 잦음 | 스크롤에 직접 묶여 매끄럽다 |
| 브라우저 지원 | 어디서나(JS라서) | 제한적 → @supports 폴백 필요 |
다른 담당자와의 연결
스크롤 애니메이션은 혼자 완성되지 않습니다. 등장 타이밍과 이징 같은 모션 설계, 끊김 없이 돌게 하는 성능 최적화, 그리고 이 모든 걸 웹표준 위에 얹는 개발 방식이 함께 가야 “부드럽고 가벼운” 결과가 나옵니다. Findable에서는 이 세 사람이 같은 페이지를 두고 대화합니다.
scroll-driven animation은 모든 브라우저에서 되나요?
JS 스크롤 리스너와 뭐가 다른가요?
view()와 scroll()의 차이는 무엇인가요?
성능에 정말 좋은가요, 과용하면 안 되나요?
JS를 완전히 버려도 되나요?
모션 담당자의 노트
등장 타이밍과 이징을 다루는 법.
성능 담당자의 노트
끊김 없이 가볍게 돌게 하는 법.
개발은 이렇게 합니다
웹표준·성능·구조화 데이터 내장.
이 글의 진행 바는 이 페이지에서 실제로 동작하는 순수 CSS scroll-driven animation입니다(미지원 브라우저는 채워진 상태로 폴백). animation-timeline: view()/scroll()은 글 작성 시점 기준 아직 Baseline이 아니며 브라우저 지원은 계속 변하므로, 적용 시점에 반드시 다시 확인해야 합니다. 성능·부드러움에 대한 설명은 일반 원칙이며 특정 성과를 보장하지 않습니다. 날조된 사례·수치는 사용하지 않았습니다.