Jump to Navigation Menu

Visualizing Intersection Observer

April 18, 2022

Intersection Observer is a tool that I am reaching for more and more as I add scroll-triggered animations to the projects I build. As much as I like ScrollTrigger from GreenSock, I find that it is not always the best solution.

I want to keep my code as light as possible. And Intersection Observer allows me to do that. I only reach for ScrollTrigger if I need to do more advanced animation work, like when I need a timeline or want the animations to be linked to the progress of the scrollbar. For simpler animation projects, it is not worth the weight that you add to the page when you can reach for Intersection Observer.

Misunderstandings

One thing I have struggled in using Intersection Observer is with understanding how to translate the concepts of using ScrollTrigger over to using Intersection Observer. When I first started using Intersection Observer, I thought I needed to adjust the threshold value to set up my “trigger.” Then I learned that threshold had to do with the percentage of the target that must be visible to trigger the callback. I had mistakenly thought that threshold had more to do with the viewport than the target element. I learned this understanding of threshold from Stephanie Eckels’ Intersection Observer tutorials in her series 12 Days of Web.

I also learned that rootMargin is the option that I needed to adjust to set up the position of my “trigger.” Stephanie gave examples of this in her tutorial but an important concept didn’t stick with me after I read her article in December.

Negative values on rootMargin

Last week, my teammate pointed me to Wilmer Soto Capobianco’s Intersection Observer Playground. This “experiment” helped me to visualize Intersection Observer and understand how to change the rootMargin values to set up the Observer to trigger my animations the way that I wanted them. ScrollTrigger has a feature that allows you to set up markers so you can visualize where the ScrollTrigger begins and ends. Wilmer’s tool helped me to understand the relationship between setting up a trigger in ScrollTrigger and setting up an observer with the Intersection Observer API.

One of the things I had failed to understand is that I needed negative values on rootMargin. I think the reason I failed to grasp that concept is that two of the tools I have used for triggering scroll-animations—Waypoints and ScrollTrigger— both use positive values in their configuration. Waypoints has an offset that is calculated from the top of the element that you set as a trigger. Similarly, ScrollTrigger uses positive values relative to the top of the viewport.

The more I thought about it, it makes sense that you would use negative values for rootMargin because a positive value would put a margin outside the root (which by default is the viewport). In order to modify the Intersection Observer, you would need a negative margin on the root so that it shrinks the area of the Intersection Observer to not be the same dimensions as the viewport.

Wilmer’s tool helped me to conceptualize that the Intersection Observer is a “box” or area and not a single point or line, much like it is in Waypoints, at least that is how I have conceptualized it when using Waypoints in the past. It is helpful to gain this understanding of the “observer” if I want to do some animations as an element no longer intersects the “observer,” such as fading out the element as you see in Wilmer’s experiment.

After gaining this understanding and then applying it by updating the option values of my Intersection Observer in a project, I went back to Stephanie’s tutorial. All the information I needed to learn about Intersection Observer was there in the article. But what really helped me to grasp it in a way that I will remember it in the future was being able to visualize the rootMargin option by playing around with Intersection Observer Playground. I am more of a visual learner so tools like this can help me understand things and conceptualize them better than just reading about them with words.

Application

In most instances, I want to trigger animations when the top of the element (or parent element) hits the vertical center of the viewport. I have found by trial and error that any more than 50% from the top, you don’t see enough of the element that you are animating to make it worth animating. And if you trigger the animation less than 50% from the top, the user might scroll past the content if the content is being animated in with opacity, which is a very common practice in today’s websites.

For my example, I want to trigger CSS animations by adding a class of “animate” to my section with an id of “triggered-element.”

If I were using ScrollTrigger, I would set up a trigger to execute when the top of the triggered element hits the center of 50% of the viewport.

const main = gsap.timeline({
  scrollTrigger: {
    trigger: '#triggered-element',
    start: "top center"
  }
});

If I was using Waypoints, I would set up the Waypoint to offset 50% from the top of the viewport.

var waypoint = new Waypoint({
  element: document.getElementById('triggered-element'),
  handler: function(direction) {
    element.classList.add('animate');
  },
  offset: '50%
})

Then for Intersection Observer, I set a rootMargin of “0% 0% -50% 0%” (top, right, bottom, left just like in CSS). Since my animation is only going to run once as the user scrolls down the page, I want to remove the observer once it has intersected and then add a class of “animate” to my element with an id of “target-element.”

if ('IntersectionObserver' in window) {
  const targetEl = document.getElementById('target-element');

  const options = {
    // by default, the root value is the viewport unless specified
    rootMargin: '0% 0% -50% 0%'
    // threshold: 0  // the default value of threshold is '0' unless specified, which means it that the IO will trigger once 1 pixel of the element intersects
  }
    
  const callback = (entries) => {
    const box = entries[0];
    if (!box.isIntersecting) return;
    boxObserver.unobserve(box.target);
    targetEl.classList.add('animate');
  }

  const boxObserver = new IntersectionObserver(callback, options);
  boxObserver.observe(targetEl);
}

And here is a visualization that rootMargin in the Intersection Observer Playground.

Once the “trigger-element” clears the blue area in the visualization, the animation will be triggered.

Conclusion

Intersection Observer is technically an experimental API, but it has support in all of the evergreen browsers so we can safely start using it today for applications like I have shown to trigger animations. It is a great tool to have because it relies on browser functionality which means you can keep your code leaner than other solutions like ScrollTrigger and Waypoints.

Stephanie Eckels’ tutorial on Intersection Observer is a great place to start. I would also recommend playing around with Wilmer Soto Capobianco’s Intersection Observer Playground as it helps you to visualize how changing threshold and rootMargin values change the observer.

Comment on this post