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 user can see them.
In modern CSS it’s now possible to link the 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 want a normal animation
that’s triggered when the element appears?
This sounds easy but there’s just no easy way to do it with plain CSS. If you look for a solution online,
everyone will tell you to just 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 it has entered the viewport. You won’t even need to write any key frames— a simple CSS
transition will do.
First add this script 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—errr, transition.
#thing {
opacity: 0;
transition: opacity 0.5s ease-in;
}
hotfx-intersection-observer:state(is-intersectiong #thing {
opacity: 1;
}
That’s the basic approach idea. Let’s see a better example.
We can use this custom element to implement a full-window photo gallery where the user scrolls between images by scrolling through captions, triggering a fade between the fixed-position images. That explanation might be hard to follow so just check out the demo.
Sweet. I love this effect because it really helps you see the images. They are never awkwardly cropped by the browser window and the user doesn’t need to scroll to precisely the right spot to enjoy them.
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;
}
}
:has(> .revealing-image) {
display: block;
animation: flipthebit linear both;
animation-timeline: view();
animation-range: contain 25% contain 25%;
}
@container style(--bit: 0) {
.revealing-image {
opacity: 0;
clip-path: inset(0% 60% 0% 50%);
}
}
@container style(--bit: 1) {
.revealing-image {
opacity: 1;
clip-path: inset(0% 0% 0% 0%);
}
}
.revealing-image {
transition: all 0.5s ease-in-out;
}
So you see we’ve got some @keyframes
, a container style query and scroll-triggered animations to make it
all work. This works great but it feels a bit verbose and confusing to me. I will take my custom element any day of the
week.
I like to think this component is done, because simpler is probably better. The one thing I might add would be
is-below
and is-above
states, in case someone wanted to change the element depending on
whether the reader is scrolling up or down.
If you’ve got suggestions or find any problems, please drop a line in GitHub.