CSS animation-trigger Property: Scroll-Triggered Animations
Scroll-triggered animations without IntersectionObserver
Playing an animation once when an element scrolls into view is the classic scrollytelling pattern. It used to require IntersectionObserver plus the Web Animations API. The animation-trigger property does the same job in pure CSS.
const io = new IntersectionObserver( (entries) => { entries.forEach( (entry) => { if (entry.isIntersecting) { entry.target.animate( [ { opacity: 0, translate: '0 32px' }, { opacity: 1, translate: '0 0' } ], { duration: 600, easing: 'ease-out', fill: 'both' }); io.unobserve( entry.target); } });});document.querySelectorAll( '.reveal').forEach( (el) => io.observe(el)); .reveal { animation: fade-up .6s ease-out both; animation-trigger: --t play-forwards; timeline-trigger-name: --t; timeline-trigger-source: view(); }@keyframes fade-up { from { opacity: 0; translate: 0 32px; } to { opacity: 1; translate: 0 0; }} scroll triggered animations Browser Support
This feature is not Baseline because it does not work in some of the most widely-used browsers.
Not ready for production without a fallback.
No JavaScript observer
The browser tracks when the element crosses into the trigger range. No IntersectionObserver setup, no observer cleanup, no animate() calls.
Distinct from scroll-driven
Scroll-driven animations scrub progress to scroll position. Scroll-triggered animations play once with their own duration when a range is entered. Use the right tool for the job.
Two-way actions
play-forwards on activation, play-backwards on deactivation. Or pair with reset, pause, and step to build reveal-and-hide patterns.
How it works
Reveal-on-scroll animations have been a JavaScript job for years. The standard recipe: spin up an IntersectionObserver, watch a set of elements, call element.animate() (or toggle a class) when one crosses the threshold, then unobserve to prevent re-triggering. It works but it lives outside CSS and runs whether or not the animation actually needs to play.
The animation-trigger property tells a regular CSS animation when to start. Set up a trigger with timeline-trigger-name and timeline-trigger-source: view(), then bind it to the animation with animation-trigger: --t play-forwards. When the element scrolls into its view range, the animation plays. The browser handles the tracking, the threshold, and the play state.
prefers-reduced-motion when you wrap them in a media query, just like any other CSS animation. Disable or shorten the motion for users who request it.