CSS Scroll-triggered Animations with Style Queries
Posted on January 27, 2024
Takes about 5 minutes to read
Topping my CSS wishlist in 2024 are scroll-driven animations and style queries. At the time of writing this post, both lack full support but I've got fingers crossed they become available in all evergreen browsers not too long from now. I had done some exploration of scroll-driven animations but have not yet spent much time with style queries beyond reading and daydreaming about the amazing possibilities they'll unlock.
I happened upon a CodePen by Jhey Tompkins that kicked off my curiosity. In that demo, as the page is scrolled, animations are triggered that smoothly highlight passages of text within the copy. It's all powered by CSS. That's incredible! I've achieved this effect in past demos using GSAP ScrollTrigger and the Intersection Observer API. How is this same concept accomplished with only CSS?
Diving into Jhey's code, we find a --highlighted custom property set to 0. Using scroll-driven animations, the value is updated to 1 as the mark element reaches the end of its animation-range. That value is passed into a calc() function that transitions a background-position property to create the highlighting effect.
This scroll-driven animation mimics intersection observer functionality. In fact, if we inspect the JS panel in the CodePen editor, we'll find that Jhey provides an intersection observer fallback for browsers that don't support CSS view progress timelines.
Scroll-driven animations and style queries join forces
That demo got me jazzed. What else might be possible with this bonafied CSS trick? Could we also trigger a @keyframes animation sequence? I've tested and scrapped a handful of ideas, deciding that it may not be feasible in scroll-driven animations. At least not without a little help from a new friend.
Style queries give us the ability to supply styling based on the value of a parent CSS custom property. Ahmad Shadeed's Style Queries deep dive demonstrates this in a variety of ways. I ran with Jhey's view progress timeline approach, "toggling" a custom property value in a @keyframes ruleset, then added a style query that triggered an animation on a child element.
How about that—it works! 🎉 Or rather, it works in browsers that support both style queries and scroll-driven animations. When unsupported, the demo falls back to displaying the text without the animations.
Note that only the animation-range end value is relevant for the trigger. Declaring animation-range-end: contain 40% instead would also work here. However, the demo includes the start value to explicitly set where the fade animation starts on the same element.
Once the .box element reaches the end of the animation-range, the trigger animation runs instantly, sets --animate: true on the element, then kicks off the elastic popup and background gradient transition on its child .text element. If the page is scrolled back up, the text recedes back to its starting position.
[...] what if you want a scroll-driven animation to stay on its endframe once it was entirely played?
Play through one and done? Sounds like an excellent option. Unfortunately, this cannot be done in CSS but Bramus created a set of scroll-driven animation utilities which includes a way to run a scroll-driven animation only once.
Updated on January 29th, 2024: Bramus shared with me his own experiment with this concept from last year. The article does an excellent job explaining how it works and I recommend checking it out. Our conclusions on this seem to be the same.
This was a fun experiment to do. However, it’s only an experiment and to me makes the case that we still need proper Scroll-Triggered Animations in the future – maybe something to work on for scroll-animations-2? 😉