Intersection Observer: How it should be used

Michael Wind
5 min readJan 21, 2021
Photo by Lukas Blazek on Unsplash

The Intersection Observer API had been created for web browsers in order to improve the performance of websites, which detect if specific elements are visible in the viewport of a browser. Before the Intersection Observer API had been available customized solutions were created for detecting that target elements intersect with the viewport or other container elements. Now with having the Intersection Observer API available in all modern browsers (except the good old Internet Explorer) the usage of libraries or customized solutions, which run on the main thread and therefore could cause performance issues, are not needed anymore. But also when using the Intersection Observer you have to take care how this API is used. In this article I will show why you have to take care and how to use the Intersection Observer in a correct way. For the demonstration of this topic I used the Angular JavaScript framework, but the advice how the Intersection Observer should be used can be followed on all other frameworks or just pure JavaScript.

In this article I will give you just a brief explanation how Intersection Observer work. A detailed explanation of this Web API can be found on MDN Web Docs.

In some of my websites I use fade-in animations of objects or lazy loading images when those objects or images are visible in viewport. To check if an element is visible in the browser's viewport, scrolling event listeners were used and calculations done on each and every scroll event, to check if the element is visible. After reading a great article in Medium about Intersection Observers I got interested, and I have read the web API in detail, because I definitely wanted to improve the runtime performance of my applications.

The main benefit of using the Intersection Observer API is that the calculation to check, if an element is visible in the viewport is not done in the main thread. The browser does the calculation for you and just calls a callback function. The callback function is executed the first time you ask for observing a target element and whenever the intersection state of an element changes. Depending on the intersection state you can decide what to do with the element then. You could change the visibility, change the color or trigger an animation.

How to create an Intersection Observer?

An Intersection Observer instance can be initiated by simply calling its constructor. The constructor requires as a parameter a callback function and optionally you can pass some options. As options the root, rootMargin and threshold can be specified.

let observer = new IntersectionObserver(this.intersectionObserverCallback, options);observer.observe(this.myElement.nativeElement);

With having the observer object available any HTML element can be observed by calling the “observe” method and passing the target element to this method. Now after calling the “observe” method the callback function initially will be called and afterwards on every intersection change.

For performance reasons it is recommended to keep the code of the callback-function as simple as possible. No time-consuming tasks should be done in the callback. The code below shows a callback, which shows or hides a HTML element depending on the “isIntersection” property of the Intersection Observer entry. Please notice that code below only considers observing one element.

intersectionObserverCallback = (entries) => {
if (entries[0].isIntersecting) {
// show the element
this.el.nativeElement.style.visibility = 'visible';
} else {
// hide the element
this.el.nativeElement.style.visibility = 'hidden';
}
};

After understanding the basic usage of Intersection Observers, I was asking myself how to observe now multiple elements in a website?

There would be two options:

  • Create an Intersection Observer instance for every observed element.
  • Create just one Intersection Observer instance and call the “observe” method multiple times.

The main question is: Which option is better?

Since my initial focus on investing time in this topic was to improve the runtime performance of my applications compared to the “old way”, I definitely wanted to find out, which of those two options is more performant. Thus, I had to create a test project with quite a lot of elements, which are observed via the Intersection Observer API. The elements are shown via the visibility CSS style as soon as 30% (threshold= 0.3) of the element is visible in the viewport.

In case you know the website of ScrollReveal. Yes, I got inspired by the way of demonstrating elements getting visible when shown in the viewport. In my demo there are in total 126 observed elements.

Project for testing multiple observed elements

In order that all those rectangles get observed by the Intersection Observer I made use of the possibility to create an attribute directive in Angular, which does the work to registers a HTML element at the Intersection Observer. But in this directive I have implemented the usage of the Intersection Observer for both of the two above mentioned options. Creating an Intersection Observer instance for each directive and creating just one single Intersection Observer for the whole application by using a separate service in Angular. With setting the “option” property to 1 or 2 either the first option of implementing the Intersection Observer or the second option will be used.

Here is a gist for the directive and the service:

The complete demo project you can open with this StackBlitz link.

In order to find out which of those two options is more efficient I did recorder the runtime performance on Chromes DevTools. While recording the performance I scrolled down through the test project so that the red rectangles get shown and hidden. This I did for both implemented options, by setting the “option” property first to 1 and afterwards to 2. The result is shown in the following picture.

Comparison of the profiling result

When comparing the scripting time, you can see that there is a significant difference between option 1 and option 2. The scripting time for multiple Intersection Observer instances is almost 3 times higher compared of using just one single instance. This means it is more effective having just a single instance of the Intersection Observer. Thus, creating multiple Intersection Observer instances should be avoided.

Limitation

The only limitation is that a single instance of the Intersection Observer is only possible, if the same configuration (root, rootMargin, threshold) is used for all observed elements. If you want to use different settings like 0.5 as threshold for one element and 0.3 as threshold for another element, you are forced to create two Intersection Observer instances.

Last words

I hope this article helps other web developers out there to get a better understanding on the usage of Intersection Observers and that some people will follow the advice of using just one Intersection Observer instance, since I have seen already some wrapper libraries, which use the Intersection Observer in a wrong way. In addition, I also hope that this article takes a small part of improving the performance of quite some websites out there to make the world better. 😉

Seriously, tell me what you think about this article by leaving a comment. I would be glad to get some feedback from other developers.

--

--