Stay up to date with new components!

Thanks for your interest!

Web Components On Fire

built with
View the source

Intersection Observer

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.

Basic Intersection Observer

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.

Setting the Threshold Attribute

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.

Different Animations, Different Thresholds

The CSS-Only Solution

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.

Roadmap

This component is beautifully simple and composable as it is, but it could of course do more.

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.