이 글은 누구를 위한 것인가
IntersectionObserver+ JavaScript로 스크롤 애니메이션을 구현 중인 개발자- CSS만으로 부드럽고 성능 좋은 스크롤 효과를 만들고 싶은 팀
- 브라우저 지원 현황을 확인하고 프로덕션 적용 여부를 판단하려는 개발자
들어가며
스크롤 연동 애니메이션은 항상 JavaScript scroll 이벤트 + requestAnimationFrame의 영역이었다. 메인 스레드를 차지하고, 성능이 나빠지면 jank(버벅임)가 생겼다.
CSS Scroll-Driven Animations는 이 작업을 CSS만으로, 그것도 합성 레이어(Compositor)에서 처리한다. 메인 스레드와 무관하게 동작해서 60fps가 안정적이다.
이 글은 bluefoxdev.kr의 CSS 최신 기능 가이드 를 참고하고, Scroll-Driven Animations 실전 구현 관점에서 확장하여 작성했습니다.
1. 핵심 개념
[Scroll-Driven Animations 3가지 개념]
1. animation-timeline: scroll()
→ 스크롤 컨테이너의 진행률에 연동
→ 0% = 최상단, 100% = 최하단
2. animation-timeline: view()
→ 뷰포트 내 요소의 가시성에 연동
→ 요소가 화면에 들어올 때 ~ 나갈 때
3. @scroll-timeline (명시적 정의)
→ 특정 범위를 타임라인으로 지정
[브라우저 지원 (2026년 기준)]
Chrome 115+: ✅ 완전 지원
Edge 115+: ✅ 완전 지원
Firefox 110+: ✅ 지원
Safari 17.4+: ✅ 지원
→ 현재 프로덕션 사용 가능
2. 스크롤 진행 바
/* 페이지 스크롤 진행률 표시 바 */
@keyframes expand-bar {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
.scroll-progress-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, #0066ff, #00cfff);
transform-origin: left;
/* 스크롤 타임라인 연동 */
animation: expand-bar linear;
animation-timeline: scroll(root block); /* 루트 요소, 세로 스크롤 */
animation-fill-mode: both;
}
/* HTML: <div class="scroll-progress-bar"></div> */
3. 요소 페이드인 (view() 타임라인)
/* 스크롤 시 요소가 뷰포트에 들어오면 페이드인 */
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(40px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.reveal-on-scroll {
animation: fade-in-up 0.6s ease-out both;
/* view() 타임라인: 요소가 뷰포트에 진입/이탈하는 시점 */
animation-timeline: view();
/* 진입 시 시작, 완전히 보일 때 완료 */
animation-range: entry 0% cover 30%;
}
/* 카드 격자에 스태거드 효과 */
.card:nth-child(1) { animation-delay: 0ms; }
.card:nth-child(2) { animation-delay: 100ms; }
.card:nth-child(3) { animation-delay: 200ms; }
.card:nth-child(4) { animation-delay: 300ms; }
.card {
animation: fade-in-up 0.5s ease-out both;
animation-timeline: view();
animation-range: entry 0% cover 25%;
}
4. 패럴랙스 효과
/* CSS만으로 구현하는 패럴랙스 */
@keyframes parallax-slow {
from { transform: translateY(-20%); }
to { transform: translateY(20%); }
}
@keyframes parallax-fast {
from { transform: translateY(-40%); }
to { transform: translateY(40%); }
}
.hero-section {
position: relative;
overflow: hidden;
height: 100vh;
}
.hero-background {
position: absolute;
inset: -20%; /* 패럴랙스 이동 공간 확보 */
animation: parallax-slow linear both;
animation-timeline: scroll(root);
animation-range: 0% 50%; /* 섹션 절반까지만 */
}
.hero-text {
position: relative;
z-index: 1;
animation: parallax-fast linear both;
animation-timeline: scroll(root);
animation-range: 0% 40%;
}
5. 실용적인 패턴 모음
/* 1. 헤더 축소 효과 (스크롤 다운 시 작아짐) */
@keyframes shrink-header {
from {
padding: 20px 40px;
background: transparent;
}
to {
padding: 12px 40px;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
box-shadow: 0 2px 20px rgba(0,0,0,0.1);
}
}
.site-header {
position: sticky;
top: 0;
z-index: 100;
animation: shrink-header linear both;
animation-timeline: scroll(root);
animation-range: 0px 200px; /* 0 ~ 200px 스크롤 범위 */
}
/* 2. 텍스트 하이라이트 (읽으면서 강조) */
@keyframes highlight-text {
from { background-size: 0% 100%; }
to { background-size: 100% 100%; }
}
.highlight {
background-image: linear-gradient(transparent 60%, #ffd700 60%);
background-repeat: no-repeat;
background-size: 0% 100%;
animation: highlight-text linear both;
animation-timeline: view();
animation-range: entry 20% entry 80%;
}
/* 3. 이미지 reveal (클립 마스크 효과) */
@keyframes image-reveal {
from { clip-path: inset(0 100% 0 0); }
to { clip-path: inset(0 0% 0 0); }
}
.reveal-image {
animation: image-reveal 0.8s cubic-bezier(0.77, 0, 0.175, 1) both;
animation-timeline: view();
animation-range: entry 0% entry 60%;
}
/* 4. 카운터 애니메이션 (숫자 증가) */
@property --num {
syntax: '<integer>';
initial-value: 0;
inherits: false;
}
.stat-number {
--num: 0;
counter-reset: num var(--num);
animation: count-up linear both;
animation-timeline: view();
animation-range: entry 20% cover 50%;
}
.stat-number::after {
content: counter(num) "%";
}
@keyframes count-up {
from { --num: 0; }
to { --num: 87; } /* 목표 숫자 */
}
6. 점진적 향상 전략
/* 미지원 브라우저 대응 */
@supports (animation-timeline: scroll()) {
.reveal-on-scroll {
animation: fade-in-up 0.6s ease-out both;
animation-timeline: view();
animation-range: entry 0% cover 30%;
}
}
/* 미지원 시 기본 상태 (모두 보임) */
.reveal-on-scroll {
opacity: 1;
transform: none;
}
/* 모션 감소 설정 존중 */
@media (prefers-reduced-motion: reduce) {
.reveal-on-scroll,
.scroll-progress-bar,
.parallax-element {
animation: none !important;
}
}
마무리
CSS Scroll-Driven Animations는 2026년 기준으로 모든 주요 브라우저에서 지원된다. JavaScript로 구현하던 스크롤 애니메이션을 CSS로 옮기면 메인 스레드 부담이 줄고, 코드도 간결해진다.
IntersectionObserver로 클래스를 토글하는 패턴은 이제 animation-timeline: view()로 대체할 수 있다. 특히 스크롤 진행 바, 요소 페이드인, 헤더 축소 효과는 바로 적용 가능하다.