Intersection Observer: A great new API

About a 9 minute read

Written by on

#api #javascript

Have you ever wanted to control an element, but have the trigger be when that element is in the viewport? For instance, if you want to lazy load an image, you’ll want to wait until that image’s container gets to the viewport, then load it. The same could be said if you want a specific animation to run when a specific element enters the viewport.

In the past, this has been something that has been difficult to achieve. In fact, any direction you took had its own set of drawbacks.

That’s where the Intersection Observer API comes in. This new API not only lets you see when a target element intersects with an ancestor’s viewport, but also has a wide array of options to customize it. For example, you could fire an event when 60% of the target element has entered the viewport.

An Old Way

Before we get to the new stuff, let’s think about how this would have to be done the old way. First of all, you need a way to detect if the target element is in the viewport. For this, there is a pretty simple function.

var isInViewport = function(elem) {
    var bounding = elem.getBoundingClientRect();
    return (
        bounding.top >= 0 &&
        bounding.left >= 0 &&
        bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
        bounding.right <= (window.innerWidth || document.documentElement.clientWidth)
    );
};

This works, but the only thing it can return is true or false. It doesn’t tell us percentages of the element in the viewport and we can’t change the parent element to check the target element against. It works, but doesn’t provide a lot of detail or customization.

The next thing we need to do is figure out how we are going to feed elements to this function to see if they are in the viewport. Probably the most common trigger would be the scroll event. So, we set up a listener for page scroll.

window.addEventListener("scroll", function() {
	// Scroll Fired
});

Of course...this fires a crap ton and can be bad for performance. One quick and dirty way to negate this performance hit is to add a timeout.

var scrollTimeout;	
window.addEventListener("scroll", function() {
	clearTimeout(scrollTimeout);
	scrollTimeout = setTimeout(function() {
		// Staggered Scroll Fired
	}, 25);
});

Now we will only run the code if there is a 25 millisecond gap in scrolling events. This helps with performance, but also adds some gotchas. If the user continuously scrolls, nothing fires. The fastest the scroll event can fire limited by the timeout. Also, on mobile, this will wait for the “momentum” scroll to end.

It works, but it’s far from perfect. This is just bare bones, there are plenty of ways to improve upon these functions, but you get the idea of how complex things can get.

Using The Intersection Observer API

Let’s see how easy it can be with this new API. First and foremost, we have to check to see if the browser supports this new API. The check for this is really simple.

if("IntersectionObserver" in window) {
	// Supported
}

Easy, right?

Let’s create something real quick that will tell us when specific elements are in the viewport.

var observer = new IntersectionObserver(function(entries, observer) {
	// This is the callback function for the observer
	entries.forEach(function(entry) {
		// Loop through each entry in the observer
		if(entry.isIntersecting) {
			// Element is in the viewport
			// Do Stuff
		}
	});
});
var els = document.querySelectorAll(".observe-me");
els.forEach(function(el) {
	observer.observe(el);
});

Tada! Isn’t that great? Let’s break down what is happening here.

First, we create a new IntersectionObserver and assign that to the observer variable. We create an anonymous callback function that will be fired when the page loads, and when the element enters the viewport. In this callback function, we check a property of each entry called “isIntersecting” to make sure the element is, in fact, in the viewport.

After the observer variable, we grab all of the elements that we want to observe. Then, we loop through each of them and tell our observer to watch out for them.

This setup could be easily implemented to do something such as lazy load images. It’s quick and simple, but there is more!

Intersection Observer Options

In the previous example, we used the default options for the observer, but it is possible to change those. In order to use those options, it would look something like the following.

var options = {
	root: element,
	rootMargin: "0px",
	threshold: 1.0
}
var observer = new IntersectionObserver(callback, options);

Let’s break down each of these.

root

The root property within options is used to specify what element you want the targeted element to be compared against. Basically, think of it as a custom viewport. Whatever element you pass into this gets treated as the “viewport” of the targeted element. This is useful if you want the trigger to be something other than the browser window. As you might expect, the browser window is the default value for this.

rootMargin

This piggybacks off of the root property. Basically, you are setting up a margin for whatever element you choose for your root. This can be treated just like the CSS property. So if you wanted to shrink the viewport area by 20px on all sides, you could put “20px 20px 20px 20px”. The default for this is 0.

threshold

Last of the observer options is the threshold. This number operates like opacity in CSS where 1.0 refers to 100%. This option allows you to adjust how much of the target element you wish to be visible before the observer’s callback is called. The default is 0, which means as soon as the tiniest pixel is in the viewport, it fires. If you wanted the entire element to be in the viewport, you could do 1.0.

entry

Within the observer’s callback we used a property on our entry that was called “isIntersecting”. This is just one of the many properties you have available to you once you have the entry within the callback. I’m not going to go through them all, but just know that you have a wide variety of information available to you based off that entry.

Browser Support

For being what it is, and how cool it is, the support for Intersection Observer is actually pretty good.

Can I Use intersectionobserver? Data on support for the intersectionobserver feature across the major browsers from caniuse.com.

As you can see, most of the up to date browsers have implemented this, which is a great thing. The continuous, nagging issue is Internet Explorer.

As with any newer technology, you are going to have to prepare some fallbacks for browsers that have yet to implement, or users who don’t keep their browsers up to date. Of course, there is a wonderous polyfill that was created by none other than the W3C.

Wrapping Up

I hope this article gave you a little bit of insight on just how powerful this new Intersection Observer API can be. I’ve only scratched the surface of it myself, using it almost exclusively for lazy loading purposes. With support being so good, and a good polyfill to back it up, there is no reason not to use this new, useful api.

The Mozilla Developer site has wonderful documentation on this subject.

Thanks for reading!!