Controlling complex animations with custom properties
I recently added a new trick to my CSS toolbelt, and I've found myself using it all over the place.
Using the power of --custom-properties and some interesting behavior of animation, here's a very clever way to scrub through a CSS animation in response to user interaction. đź’ˇ
Credit where credit is due— I first saw this trick in use on the Tornis site by Robb Owen. Scott Kellum created the technique.
I’m just spreading the word.
Here’s the secret—animation-delay accepts negative values. A negative value begins the animation immediately (like animation-delay: 0), but starts playing partway through its cycle.
By combining this trick with animation-play-state: paused, we can control the animation directly with a custom property.
This trick is incredibly useful for complex hover animations, coordinating color changes across elements, or proximity feedback.
.my-element {
/* Setup */
animation-name: spin;
animation-timing-function: linear;
/* Here's the magic */
animation-play-state: paused;
animation-duration: 1s;
animation-delay: calc(var(--progress) * -1s);
/* These clean up some weirdness */
animation-iteration-count: 1;
animation-fill-mode: both;
}
If you’re looking for a one-liner, try this animation shorthand.
.my-element {
/* Binds `spin` animation to var(--progress) */
animation: spin 1s calc(var(--progress) * -1s) paused linear 1 both;
}
To keep it even simpler, you could use a --bind-animation “mixin” (by the way, is Houdini ready yet?)
:root {
--bind-animation: paused linear 1 both;
}
.my-element {
animation: spin 1s calc(var(--progress) * -1s) var(--bind-animation);
}
Or a real Sass mixin.
@mixin scrub-animation-on($property) {
animation: 1s calc(#{$property} * -1s) paused linear 1 both;
}