Track Element Visibility Using Intersection Observer

Hero image for Track Element Visibility Using Intersection Observer. Image by Denys Rodionenko.
Hero image for 'Track Element Visibility Using Intersection Observer.' Image by Denys Rodionenko.

Historically, tracking an element's position within a user's viewport has been a complicated and arduous task of listening to scroll and resize events and then calculating an element's position using Element.getBoundingClientRect(). However, in the realm of modern web development, the Intersection Observer API stands out as a powerful tool for managing element visibility within the viewport, without the need for intensive inbrowser event listeners and calculations.


What is Intersection Observer?

Intersection Observer is a JavaScript API that allows us to asynchronously observe changes in the intersection (overlap) of an element with an ancestor element or the viewport. It's particularly useful for tasks like lazy loading images, implementing infinite scrolling, or tracking advertisement visibility.

I use it here on my personal website in a few different places, perhaps the most obvious being the clap buttons at the bottom of each article and portfolio project. Because I don't want my visitors to have to connect to the claps endpoint immediately on pageload, I use Intersection Observer to detect when a visitor is scrolling near to the button, and ony then trigger the API call as it gets close to coming into view.


How to Use Intersection Observer

A Basic Example

interface IntersectionObserverCallback {  (entries: IntersectionObserverEntry[], observer: IntersectionObserver): void;}const callback: IntersectionObserverCallback = (entries, observer) => {  entries.forEach((entry) => {    if (entry.isIntersecting) {      console.log('Element has entered the viewport');    }  });};const observer = new IntersectionObserver(callback);const targetElement = document.getElementById('target');if (targetElement) {  observer.observe(targetElement);}

In this very simple example, I've defined a callback that logs a message whenever the targeted element enters the viewport. The observer is then set to monitor targetElement.

A More Detailed Example

The code above is all very well and good if you just want to trigger an event every time an element is scrolled into view. However, Intersection Observer offers a lot more configurability than that, and makes it much more useful for realworld uses where for example you might only want to trigger the event once and then stop listening, or might want to trigger it when it is a certain percentage either within or outside the viewport.

So, in this example, we'll configure the Intersection Observer to observe an element with specific margins and thresholds. We'll also set it up to unobserve the target after the first intersection.

interface IntersectionObserverCallback {  (entries: IntersectionObserverEntry[], observer: IntersectionObserver): void;}const advancedCallback: IntersectionObserverCallback = (entries, observer) => {  entries.forEach((entry) => {    if (entry.isIntersecting) {      console.log('Element is intersecting with the viewport');      // This is where you trigger any functionality you want for when the      // element intersects      // Stop observing after the first intersection      observer.unobserve(entry.target);    }  });};// Configuration options for the Intersection Observerconst options = {  rootMargin: '10px 20px 30px 40px',  // top, right, bottom, left margins around the root  threshold: [0, 0.5, 1],  // 0%, 50%, 100% of the target's visibility};const advancedObserver = new IntersectionObserver(advancedCallback, options);const targetElement = document.getElementById('advanced-target');if (targetElement) {  advancedObserver.observe(targetElement);}

The Code Explained

This is a much more nuanced use of Intersection Observer which showcases some of the features you might want or expect when tracking element visibility:

  1. Callback Function
    : The advancedCallback function checks if the target element is intersecting with the viewport. If it is, it logs a message and then stops observing the element using observer.unobserve(entry.target). Any functionality you wanted to trigger when the element is visible onscreen, goes above this unobserve.
  2. Options Object
    : I've defined an options object that specifies:
    1. rootMargin: Margins around the root element. In this case, we set different values for each side (top, right, bottom, left), which effectively grows or shrinks each side of the root element's bounding box before we compute it's intersections.
    2. threshold: An array of thresholds at which to trigger the callback. Each element in the array is a ratio (ranging from 0 to 1) of the target element's visibility. The callback is executed when any of these thresholds are crossed.
  3. Observer Creation
    : An IntersectionObserver is created with the advancedCallback and the options. It's then used to observe targetElement.
  4. Once and Stop
    : The observer is configured to unobserve the target element after the first intersection is detected, making it a "once and then stop" observer. This is useful for scenarios like lazyloading images where you only need to know when they first appear onscreen.

Replacing Traditional Methods

As I touched upon at the start of this article, historically developers would have to rely on event listeners for scroll and resize events to determine whether an element was visible in the viewport or not. However, this approach can lead to performance issues:

  • Scroll Event Issues

    : Continuously handling scroll events can result in janky animations and unresponsive scrolling, especially if the event handler includes heavy computations or DOM manipulations.
  • Debouncing/Throttling Limitations

    : Whilst using debouncing or throttling with scroll events can reduce performance issues, they still do not provide an optimal solution because as the events are triggered and processed regardless of whether the element is in the viewport or not.

Performance Benefits of Intersection Observer

  • Efficient Element Tracking

    : It efficiently tracks elements' visibility within the viewport without the overhead of constant event handling.
  • Reduced Unnecessary Checks

    : Intersection Observer only runs the callback when there is an actual change in the intersection, reducing unnecessary checks and computations and simplifying the codebase.
  • Browser Optimisation

    : The browser can optimise the performance of the Intersection Observer internally, making it inherently more efficient than manual event listeners.

The Wrap‑Up

JavaScript's Intersection Observer API offers a modern, efficient approach to handling element visibility in the viewport. By replacing traditional methods like constant scroll or resize event listening, we can achieve better performance and smoother user experiences.


Categories:

  1. Development
  2. ES6
  3. Front‑End Development
  4. Guides
  5. JavaScript