Frontline Skirmishes, Part 2: IntersectionObserver


Modern web apps more and more often use the mechanism of observing changes in visibility of specific items on a page. The most important benefit is known as “lazy loading”, which means automatic loading of additional content as the user is browsing and scrolling the page. In the previous post, I discussed a sample implementation based on ScrollEvent. This time however, I will tell you about a more recent solution which is optimal in most cases — IntersectionObserver.

IntersectionObserver

The IntersectionObserver can be used to monitor the visibility of a website element in up-to-date versions of all modern browsers. It cannot be used directly in Internet Explorer (still kicking), but you can get around this problem by using a dedicated Polyfill.

IntersectionObserver can be used in this way, for example:

First, we define a configuration object which consists of the following elements:

  • Root: the element we will refer to when monitoring the visibility. If not defined or null, the root is the browser window.
  • RootMargin: the width of the margin around the observed element to be included in the visibility calculation. It allows you to define margins on each side as a number of pixels or a percentage, just like in CSS. By default, the margins are set to 0. For example, setting the width of the top margin to 50px allows you to receive a notification in a situation where the user, when scrolling the page, comes within 50 pixels of the observed element.
  • Threshold: a level or an array of several degrees of visibility (numbers between 0 and 1), beyond which the IntersectionObserver should trigger a callback. For example, a threshold of 0 will trigger a callback when any part of an element becomes visible, while a threshold of 1 will trigger the event only when an element becomes fully visible.

Next, we build our IntersectionObserver, providing the callback and the configuration object as parameters.

Last but not least, we have to use the “observe” method to tell the IntersectionObserver which HTML element we want to observe. After that, all that remains is to wait for calls to the callback method through IntersectionObserver.

Below you will find a library that uses the IntersectionObserver to monitor the visibility of elements in a way which is slightly simpler and more in line with the Angular approach.

Each time, our callback function receives several interesting values in the IntersectionObserverEntry event, the most important of which are:

  • isIntersecting: boolean — true, if the element has become visible (passed the given visibility threshold)
  • intersectionRatio: number — a value between 0 and 1 indicating the degree of visibility
  • target: HTMLElement — the observed element for the notification (an important parameter when the same observer observes more than one element)

IntersectionObserver as an Observable

IntersectionObserver can be used in the simple way described above, but in an Angular app it can be done in a much more “Angular” way, using RxJs and a directive. I will describe a library that does it this way.

The most important part of the library is the service that provides the functionality of the IntersectionObserver in the form of an Observable object.

Our Observable object calls the IntersectionObserver to emit notifications about a change in visibility for the selected HTML element.

In addition to the standard set of parameters, we also have an optional stopWhenVisible parameter which allows you to automatically stop observing a given element as soon as it becomes visible. Such behaviour is preferred, for instance, when we need to load specific content at a specific location on a page after the user reaches a specific place, and then we no longer want to monitor this element.

You can subscribe to the Observable object above to monitor the visibility of an element, but it is easier and more convenient to use a directive.

IntersectionObserver as a directive

Creating a directive using an Observable object provided by the service comes down to calling the subscription with the appropriate parameters and terminating it in ngOnDestroy.

To monitor the visibility using the directive shown above, just add the directive with its callback parameter to the attributes of the selected HTML element, as in the example below:

Additionally, you can provide optional configuration parameters for the IntersectionObserver [intersectionRootMargin], [intersectionRoot], [IntersectionThreshold] and [stopWhenVisible]=’true’.

Optimization

The code described above is an example of a simple implementation that creates a separate IntersectionObserver for each observed HTML element.

If we need to monitor many elements in the same way (i.e. with the same configuration), it is better to use a common IntersectionObserver object. This optimizes the use of resources by our application but requires implementing some additional logic.

Additional mechanisms must then check if an IntersectionObserver with given parameters already exists and, depending on the outcome, add an element to the existing subscription or create a new IntersectionObserver. Similarly, ngOnDestroy must check if a specific IntersectionObserver observes more than one element and, depending on the outcome, it must either stop observing this element or completely terminate the subscription and stop the IntersectionObserver.

To more easily observe multiple elements using the same IntersectionObserver, we can use the RxJS share() operator which ensures the shared subscription is terminated at the right moment.

RxJS allows you to easily implement an additional type of optional optimization which limits how often the events are emitted. For this purpose, you can add an additional throttleTime parameter to the directive described above that specifies the minimum interval in milliseconds, and connect the operator with the same name to the subscribed Observable.

Code and npm

The complete code with all the optimizations mentioned above is available on GitHub:

https://github.com/piotr-zysk/LabIntersectionObserver/tree/master/projects/pz-intersection-observer

You can also download it as an npm package:

To ensure compatibility with older browsers, the shared library uses polyfill.

Summary

The IntersectionObserver allows you to monitor the visibility of elements on the page in a simple and effective way. Compared to classic solutions based on ScrollEvent, the IntersectionObserver does not strain the machine as much, which means the app will work better on older computers. But most importantly, it does not require the implementation of complex, complicated and error-prone logic. We don’t have to worry about the various aspects of visibility monitoring. We just need to connect the IntersectionObserver, preferably using the directive discussed above. In all cases, the built-in mechanisms will take care of the correct operation.


Piotr Zyśk

About Piotr Zyśk

Fullstack .Net/Angular developer in Atena since 2019. Previous 16 years spent on IT support for large contact center company, including development and maintenance of local infrastructure. After work I like to spend time on my hobbies, like photography, composing music on my PC or playing virtual racing games. You can also meet me biking in the nearby forests.

Leave a comment

Your email address will not be published. Required fields are marked *