I am looking to make a CSS animation start running once the parent of the element I want to animate is fully scrolled into view with pure CSS using the View Timeline (currently only working in Chrome 115+).
A bit of background on what I am attempting to do here, illustrated with a simple :hover example. Let's say I want to make an element spin infinitely, but only while it's hovered, the animation is paused otherwise.
I can do something like this:
div {
margin: 2em auto 0;
width: 8em;
aspect-ratio: 1;
background: color-mix(in hsl, hotpink calc(var(--bit, 0)*100%), gold);
animation: rot 2s linear infinite;
animation-play-state: var(--bit, paused)
}
@keyframes rot { to { rotate: 1turn } }
div:hover { --bit: 1 }
<div>Hover me!</div>
The animation-play-state is set to the custom property --bit, which hasn't been explicitly set anywhere (this is important), but has a fallback value of paused. So the animation is paused while the element isn't hovered.
If the element is hovered, then --bit is explicitly set to 1, which is an invalid value for animation-play-state. So then animation-play-state reverts to its initial value, which is running.
I prefer this technique because it allows me to change the values of multiple properties on :hover by only setting --bit to 1 (in the example above, I'm also changing the background using --bit and other properties can be changed as well, but I wanted to leave this example as simple as possible).
Back to triggering animations on scroll. Let's say we have a header and a section, both 100vh tall. The section contains the element we want to animate. When this section containing our element has fully (100%) entered the viewport, we change the value of --bit to 1, which is an invalid value for animation-play-state, so it should make it revert to initial value, running, which should make the rotation start. Except that doesn't happen.
* { margin: 0 }
header, section {
display: grid;
place-items: center;
height: 100vh
}
section {
animation: b linear both;
animation-timeline: view();
animation-range: entry 100% entry 100%
}
@keyframes b { to { --bit: 1 } }
div {
width: 9em;
aspect-ratio: 1;
background:
color-mix(in hsl, hotpink calc(var(--bit, 0)*100%), gold);
animation: rot 2s linear infinite;
animation-play-state: var(--bit, paused)
}
@keyframes rot { to { rotate: 1turn } }
<header>
<h2>Scroll!</h2>
</header>
<section>
<div></div>
</section>
You can see that when the section has fully entered into view, the value of --bit has been explicitly set to 1 and it also has been used to alter the background from gold to hotpink.

However, the element hasn't started rotating. And if we check the computed value for animation-play-state, it's still paused, even though the computed value for --bit is now 1.

What is happening here? How can I make this work? Am I doing something wrong? Is this a bug?
Triggering transitions this way works just fine.
* { margin: 0 }
header, section {
display: grid;
place-items: center;
height: 100vh
}
section {
animation: b linear both;
animation-timeline: view();
animation-range: entry 100% entry 100%
}
@keyframes b { to { --bit: 1 } }
div {
width: 9em;
aspect-ratio: 1;
rotate: calc(var(--bit, 0)*1turn);
background: color-mix(in hsl, hotpink calc(var(--bit, 0)*100%), gold);
transition: 2s;
transition-property: rotate, background-color
}
<header><h2>Scroll!</h2></header>
<section><div></div></section>
Using a style query to change the value for animation-play-state works fine as well. You can see that as soon as you scroll back up a bit from being scrolled all the way, the animation pauses and the background changes from hotpink back to gold.
* { margin: 0 }
header, section {
display: grid;
place-items: center;
height: 100vh
}
section {
--bit: 0;
animation: b linear both;
animation-timeline: view();
animation-range: entry 100% entry 100%
}
@keyframes b { to { --bit: 1 } }
div {
width: 9em;
aspect-ratio: 1;
background: color-mix(in hsl, hotpink calc(var(--bit)*100%), gold);
animation: rot 2s linear infinite;
}
@container style(--bit: 0) {
div { animation-play-state: paused }
}
@keyframes rot { to { rotate: 1turn } }
<header><h2>Scroll!</h2></header>
<section><div></div></section>
But I find this clunky and I'd really rather find a way to get the invalid value technique to work. Any ideas on how to do that?
I am not sure but your issue is probably related to this:
However, any custom property used in a @keyframes rule becomes animation-tainted, which affects how it is treated when referred to via the var() function in an animation property. ref
The --bit is used in a keyframes and is defined within an animation property so is concerned about that part but I never understood what "animation-tainted" means.
Maybe if you define the custom property using @property it will work (I cannot try it)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With