왜 지금 animation-trigger를 봐야 할까요?
스크롤에 반응하는 애니메이션을 만들 때마다 우리는 습관처럼 Intersection Observer를 꺼내 들었죠. 물론 잘 동작합니다. 그런데 문제는 애니메이션 로직과 관찰 로직이 분리되어 있다는 점이에요. observer 콜백에서 클래스를 붙이고, CSS에서 그 클래스에 애니메이션을 걸고... 솔직히 말하면 "스크롤되면 페이드인" 같은 단순한 요구사항치고는 배선이 너무 많아요.
animation-trigger는 이 배선을 CSS 안으로 완전히 끌어들입니다. 트리거 선언, 활성 범위, 재생/역재생 액션까지 전부 CSS 속성으로 표현되죠. 다만 중요한 전제가 있어요.
⚠️ 실험적 기능입니다. 글 작성 시점 기준 **Chrome 145+**에서만 동작하며, 스펙은 아직 Editor's Draft 단계예요. 프로덕션에 그대로 넣기 전에 반드시 브라우저 지원을 확인하세요.
이 글에서는 timeline-trigger와 animation-trigger를 조합해 스크롤 트리거 애니메이션을 만드는 방법을 실전 코드로 정리해 드릴게요. (근거자료: CSS-Tricks - animation-trigger)
한 줄 요약: 스크롤 트리거 vs 스크롤 드리븐
많이 헷갈리는 부분이라 먼저 짚고 갈게요.
- Scroll-driven (스크롤 드리븐): 스크롤 위치에 애니메이션 진행률이 1:1로 묶여 있음. 스크롤을 멈추면 애니메이션도 멈춤. "시작 시점"이라는 개념 자체가 없어요.
- Scroll-triggered (스크롤 트리거): 트리거가 on/off 이진 상태를 가지고, 조건이 충족되는 순간 애니메이션이 **발사(fire)**됨. 이후로는 일반 CSS 애니메이션처럼 독립적으로 재생돼요.
animation-trigger는 후자, 즉 트리거 방식을 위한 속성입니다.

핵심 문법과 실전 코드
1) timeline-trigger로 트리거 정의하기
먼저 "언제 발동할지"를 정의하는 트리거를 만들어야 해요. timeline-trigger 숏핸드로 한 줄에 정리하는 게 일반적입니다.
/* 트리거 요소: 뷰포트에 완전히 들어오면 활성화 */
.trigger {
/* name / source / activation-range / active-range */
timeline-trigger: --trigger scroll() contain / cover;
}
--trigger:animation-trigger에서 참조할 커스텀 대시 식별자(dashed ident)scroll(): 스크롤 진행 타임라인을 소스로 사용contain: 요소가 완전히 보일 때 트리거 ONcover: 요소의 일부라도 보이는 동안 트리거 유지
💡 순서를 바꿀 수 없습니다.
background나border숏핸드와 달리timeline-trigger는 값의 순서가 의미를 가집니다.
2) animation-trigger로 애니메이션 연결하기
이제 실제로 애니메이션을 재생할 요소에 트리거를 붙입니다.
.text {
/* --trigger가 활성화되면 애니메이션 재생 */
animation-trigger: --trigger play;
animation: fade 0.6s ease-out;
}
@keyframes fade {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
3) 액션 조합으로 완전히 다른 동작 만들기
같은 트리거를 써도 액션만 바꾸면 전혀 다른 UX가 나옵니다. 이게 이 속성의 진짜 매력이에요.
/* 진입 시 정방향, 이탈 시 역방향 */
.card-a { animation-trigger: --trigger play-forwards play-backwards; }
/* 한 번만 재생, 이후 트리거 무시 */
.card-b { animation-trigger: --trigger play-once; }
/* 진입 시 리셋 후 재생 (스크롤 반복 시 매번 등장) */
.card-c { animation-trigger: --trigger replay; }
/* 진입 시 정지, 이탈 시 원위치 */
.card-d { animation-trigger: --trigger pause reset; }
주요 액션을 정리하면 다음과 같아요.
| 액션 | 동작 |
|---|---|
play | 현재 재생 속도로 재생 |
play-forwards | 정방향 재생 |
play-backwards | 역방향 재생 |
play-once | 초기/일시정지 상태에서만 1회 재생 |
pause | 현재 위치에서 정지 |
reset | 진행률을 0으로 되돌리고 정지 |
replay | 진행률을 0으로 되돌리고 즉시 재생 |
none | 아무것도 하지 않음 |
4) 트리거와 애니메이션은 다른 요소에 둬도 됩니다
실무에서 진짜 유용한 포인트예요. 부모에 트리거를 정의하고, 여러 자식에 animation-trigger를 걸면 부모가 뷰포트에 들어오는 순간 자식들이 한꺼번에 애니메이션됩니다.
.section {
timeline-trigger: --section scroll() contain / cover;
}
.section .item {
animation-trigger: --section play;
animation: fade 0.5s ease-out both;
}
리스트 형태의 카드 UI를 순차적으로 등장시킬 때 Intersection Observer를 여러 개 만들 필요가 없어지는 거죠.

주의사항과 실무 판단
1) 트리거 스코프는 기본적으로 전역입니다
같은 이름의 트리거를 여러 요소에서 정의하면, 캐스케이드에서 나중에 오는 요소가 이깁니다. 이걸 원치 않으면 trigger-scope로 특정 DOM 서브트리에 트리거를 가둬야 해요.
.section {
trigger-scope: --section; /* 이 서브트리 안에서만 유효 */
}
2) active-range는 activation-range를 포함해야 합니다
activation-range가 트리거가 켜지는 조건, active-range가 트리거가 유지되는 조건인데요. active-range가 activation-range를 포함하지 않으면 트리거가 켜지지 않습니다. 이 부분에서 삽질하는 분들이 많아요.
/* ❌ 잘못된 예: active가 activation을 포함하지 않음 */
timeline-trigger: --bad scroll() cover / contain;
/* ✅ 올바른 예 */
timeline-trigger: --good scroll() contain / cover;
3) 한국 SI/자사몰 환경에서의 현실적인 적용
솔직히 말씀드리면, 국내 SI 프로젝트나 자사몰에서는 당장 프로덕션 적용이 어렵습니다. 이유는 명확해요.
- 크롬 145+ 한정 지원이라 사파리(iOS), 파이어폭스, 국내에서 여전히 비중이 높은 구형 크롬에서 동작하지 않습니다.
- 국내 이커머스는 여전히 iOS Safari 비중이 20~30% 수준이라, 순수 CSS로만 가면 iOS에서 애니메이션이 아예 안 뜹니다.
- 스펙이 Editor's Draft라서 문법이 바뀔 가능성이 있습니다.
실무에서 쓸 수 있는 절충안은 이렇습니다.
/* 기본은 정적 상태, 지원 브라우저에서만 트리거 활성화 */
.text {
opacity: 1; /* 기본은 보이게 */
}
@supports (animation-trigger: none) {
.text {
opacity: 0;
animation-trigger: --trigger play;
animation: fade 0.6s ease-out forwards;
}
}
@supports로 감싸면 미지원 브라우저에서는 그냥 콘텐츠가 보이고, 지원 브라우저에서만 애니메이션이 붙습니다. 폴백 관점에서 이게 가장 안전한 패턴이에요.
4) 접근성 관점에서 반드시 챙겨야 할 것
스크롤 트리거 애니메이션은 모션 민감 사용자에게 부담이 됩니다. prefers-reduced-motion 대응은 선택이 아니라 필수예요.
@media (prefers-reduced-motion: reduce) {
.text, .section .item {
animation: none;
opacity: 1;
transform: none;
}
}
시맨틱 마크업과 접근성을 함께 고민하는 흐름은 CSS로 완벽한 파이차트 만들기: 시맨틱과 접근성을 고려한 실전 가이드에서도 자세히 다뤘으니 참고하시면 좋아요.

마무리: 지금 배워두면 1~2년 뒤에 빛을 봅니다
animation-trigger는 "JS 없이 스크롤 애니메이션을 CSS만으로" 라는 오래된 숙원을 해결하는 속성이에요. 지금 당장 프로덕션에 넣을 수는 없지만, 방향성은 명확합니다. :has(), 컨테이너 쿼리, @scope처럼 **"CSS가 삼키는 JS 영역"**의 연장선에 있거든요.
다음 단계 학습 방향
timeline-trigger숏핸드 문법을 손에 익히기 — 순서가 의미를 가지니 반복 연습이 필요합니다.- Scroll-driven animations와 비교 실험 — Chrome 팀의 timeline ranges visualizer로 두 방식의 차이를 눈으로 확인해 보세요.
@supports폴백 패턴 체화 — 실험적 CSS를 다룰 때의 기본기입니다.prefers-reduced-motion대응을 습관화 — 접근성은 나중에 붙이는 게 아니라 처음부터 넣는 겁니다.
함께 보면 좋은 글
- 메신저에서 링크를 안전하게 확인하는 방법: 메타의 프라이버시 보호 기술 ABP 심층 분석 — 브라우저/플랫폼 레벨에서 사용자 경험과 프라이버시를 다루는 관점이 궁금하다면.
- CSS로 완벽한 파이차트 만들기: 시맨틱과 접근성을 고려한 실전 가이드 — 순수 CSS로 시각화를 구현하는 또 다른 실전 사례.
실험적 스펙을 미리 익혀두는 건 **"나중에 표준이 됐을 때 이미 아는 사람"**이 되기 위한 가장 저렴한 투자예요. 지금은 데모로만 가지고 놀아도 충분합니다. 😄