Web Components On Fire
If an animation happens off screen and no one can see it, does anything actually change? Maybe it’s better not to chance it and only run your animations when the reader can actually see them.
In modern CSS it will soon be possible to link the user’s scroll to animations via the new
view-timeline
property. Animations advance forward and backward as your reader scrolls up and down. But
what if we just want a normal animation that’s triggered when the element appears?
This sounds easy enough, but there’s no easy way to do it with plain CSS. If you look for a solution
online, everyone will tell you to trigger the animation with JavaScript using an IntersectionObserver
. I
love writing JavaScript, but I love not having to write it even more.
The <hotfx-intersection-observer>
custom element wraps any element and let’s you change
CSS properties depending on whether that element has entered the browser’s viewport. You won’t even need to
write any key frames— a simple CSS transition will do.
Here’s how to trigger your own animations based on scroll. First add the script for the custom element to your page:
Then wrap the element you want to animate inside the custom element.
<hotfx-intersection-observer>
<div id="thing">Animated thing</div>
</hotfx-intersection-observer>
You can now use the :state(is-intersecting)
pseudo class to trigger your animation—or, in this case,
transition.
#thing {
opacity: 0;
transition: opacity 0.5s ease-in;
}
hotfx-intersection-observer:state(is-intersectiong #thing {
opacity: 1;
}
If you want to only run the animation the first time the element comes into view, you can use
:state(has-intersected)
, which will be added when the element slides into the viewport but not removed when
it slides out.
That’s the basic approach idea. Obviously, this can trigger any animation so it’s really up to you to make something great out of it.
In the basic example, the animation will be played once the entire element is in view. However, we can get a little more
control by using the threshold
attribute, which works just like the threshold option in the JavaScript Intersection
Observer.
So, for example, if we’d like to run the animation when the element becomes 50% visible, we can set
threshold=.5
.
There is actually a CSS-only solution to this problem. Props to Ryan Mulligan and Bramus Van Damme for independently inventing this technique. It looks something like this:
@keyframes flipthebit {
from {
--bit: 0;
}
to {
--bit: 1;
}
}
#parent {
animation: flipthebit linear both;
animation-timeline: view();
animation-range: entry 100% entry 100%;
}
#fade-in {
transition: all 0.5s ease-in-out;
opacity: 0;
}
@container style(--bit: 1) {
#fade-in {
opacity: 1;
}
}
You can see that we wrap the element in another element, add some @keyframes
to control a custom property,
use a scroll-triggered animation to kick it off and then a container style query to read the property and apply the
transition styles. This works great (or rather it will once animation-time
is widely supported), but it
feels a bit verbose and confusing to me. I will take my custom element any day of the week.
This component is beautifully simple and composable as it is, but it could of course do more.
is-below
and is-above
states, so you change the element depending on whether the
reader is scrolling up or down.
is-entering
or is-exiting
states to know whether the element is about to leave the
viewport or about to enter.
root
, rootMargin
and scrollMargin
options in JavaScript.
target
attribute that let’s an element observe another element’s visibility.Would any of that be useful? If you’ve got suggestions or find any problems, please drop a line in GitHub or email fx@hot.page.