Introduction

The Intersection Observer API has many practical uses (like this table of contents if you're on desktop!).

It provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.

Loading use-case diagram

Reference[1]

Browser Support

For most modern browsers, the Intersection Observer API is well-supported. However, it's worth noting that this API is not supported in Internet Explorer.

Check out the list of supported browsers.

If you need to support IE, you can consider using a polyfill for the Intersection Observer API. A polyfill is a piece of code that provides the technology that you expect the browser to provide natively.

However, keep in mind that while a polyfill can help bridge the gap for unsupported browsers, it may not offer the same performance benefits as the native API.


Polyfills typically can't leverage browser optimizations in the same way that native APIs can, which can lead to slower performance.

Performance Impact

Let's say you're working on a feature where you need to display or change the content of a page when a certain element becomes visible in the viewport.

This could be a 'load more' feature, an animation trigger, or a variety of other interactive experiences.

You might approach this using scroll events and the getBoundingClientRect method, something like this:

    *.js    
// Get the target element
const targetElement = document.querySelector('.target');

// Function to check if element is in viewport
function isInViewport() {
  const rect = targetElement.getBoundingClientRect();
  const windowHeight = (window.innerHeight || document.documentElement.clientHeight);
  const windowWidth = (window.innerWidth || document.documentElement.clientWidth);

  // Check if element is in viewport
  const isInView = (
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= windowHeight &&
    rect.right <= windowWidth
  );

  if (isInView) {
    console.log('Element is in the viewport!');
  } else {
    console.log('Element is not in the viewport.');
  }
}

// Listen for scroll event and check if element is in viewport
window.addEventListener('scroll', isInViewport);

This approach works, but it can be resource-intensive as it's constantly checking the position of the element during scroll events, which can lead to less-than-smooth animations or interactions.

Let's consider the performance benefits of using the Intersection Observer API:

Performance Impact compared to Scroll Events
FeatureIntersection ObserverScroll Event Listener
Main ThreadRuns off the main thread, doesn't block other tasks.Runs on the main thread, can block other tasks.
OverheadEfficiently tracks visibility of elements, runs only when visibility changes.Checks position of elements on every scroll event, can be resource-intensive.
Browser OptimizationCan be optimized by browsers, e.g., doesn't run when page is offscreen or minimized.Can't be optimized in the same way, runs every time a scroll event occurs.
Ease of UseEasier to use, requires less code for tracking visibility of elements.Can require more code and be more complex to implement.

Intersection Observer API

Syntax Overview

Here's a basic example of how to use the Intersection Observer API:

    *.ts    
// Define the callback function
const callback: IntersectionObserverCallback = (entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      console.log('Element is in the viewport!');
    } else {
      console.log('Element is not in the viewport.');
    }
  });
};
    *.ts    
// Create a new Intersection Observer instance
const observer: IntersectionObserver = new IntersectionObserver(callback);

// Get the target element
const targetElement: Element | null = document.querySelector('.target');

// Start observing the target element
if (targetElement) {
  observer.observe(targetElement);
}

Using requestIdleCallback

The Intersection Observer API specification suggests using requestIdleCallback() to defer non-essential work.

This method allows you to schedule a function to be called during the browser's idle periods, which can help to improve performance by reducing the load on the main thread[2].

In the context of Intersection Observer, you might use requestIdleCallback() in your callback function to defer tasks that don't need to happen immediately.

This could be particularly useful when you're dealing with a large number of elements or complex animations that could potentially cause jank.

Here's an example of how you might use requestIdleCallback() with Intersection Observer:

    *.ts    
const observer = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // We're intersecting, but let's defer our work using requestIdleCallback
      window.requestIdleCallback(() => {
        // Do something with `entry.target`
      });
    }
  });
}

This allows you to perform background or low priority work on the main event loop, without impacting latency-critical events such as animation and input response.

Options Param

Options Syntax Overview

The options parameter in the Intersection Observer API is an object that allows you to customize the way the observer works. It can have the following properties: root, rootMargin, and threshold.

Code will be provided in the next section.

    *.ts    

// Define the options object
const options: IntersectionObserverInit = {
  root: null, // use the document's viewport as the container
  rootMargin: '0px', // no margins
  threshold: 0.5 // callback will be run when 50% of the target is visible
};

// Create a new Intersection Observer instance w/ options param
const observer: IntersectionObserver = new IntersectionObserver(callback, options);

Root

The root option in the Intersection Observer API refers to the element that is used as the viewport for checking the visibility of the target element[3];

Root Option Diagram

If not specified or if set to null, it defaults to the browser viewport.

For example, if you want to observe when an element becomes visible within a scrolling div, you would set the root to that div.

    *.js    
let options = {
  root: document.querySelector('#scrollArea')
};

let observer = new IntersectionObserver(callback, options);

Thresholds

The threshold option is a number or an array of numbers between 0.0 and 1.0, which indicate at what percentage of the target's visibility the observer's callback should be executed [4].

Threshold Param Diagram

For example, if you set a threshold of 0.5, the callback will run when 50% of the target is visible in the viewport (or root).

    *.js    
let options = {
  threshold: 0.5
};

let observer = new IntersectionObserver(callback, options);

RootMargin

The rootMargin option is a string with syntax similar to the CSS margin property.

For example, if you set a rootMargin of 10px, the callback will run when the target is within 10px of the edge of the viewport (or root).

    *.js    
let options = {
  rootMargin: '10px'
};

let observer = new IntersectionObserver(callback, options);

It can be used to grow or shrink each side of the root element's bounding box before computing intersections, effectively giving you a way to specify a "margin" for the root (or viewport).

This table summarizes how positive and negative rootMargin values influence the intersection area and the timing of the Intersection Observer's callback trigger:

rootMargin ValueEffect on Intersection AreaWhen Callback is Triggered
PositiveExpands the areaBefore the target enters the viewport or after it starts to leave
NegativeShrinks the areaAfter the target is further into the viewport or before it starts to leave

For instance, if we wanted to increase the top and bottom intersection areas by 15%:

Root Margin Diagram

Expanding the intersection area by 15% at the top and bottom

Show Me The Code

Here's a Stackblitz sandbox with an example using IntersectionObserver displaying an overlay over the intersecting area:

Viewport as Root

This example does not declare a root, so viewport is used instead:

Open Editor 💻 - Intersection Observer w/o root

With Root Element

Compare to a root element defined in options param:

Open Editor 💻 - Intersection Observer w root

Reference[5]


  1. https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver ↩︎

  2. https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback ↩︎

  3. https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/root ↩︎

  4. https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/thresholds ↩︎

  5. https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/rootMargin ↩︎