← Back to Notes

Intersection Observer

Hamed Bahram /
5 min read--- views

The Intersection Observer API 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.

Intersection information is typically used for:

  • Lazy-loading of images or other content as a page is scrolled.
  • Implementing "infinite scrolling" websites, where more and more content is loaded and rendered as you scroll so the user doesn't have to flip through pages.
  • Reporting of visibility of advertisements to calculate ad revenues.
  • Deciding whether or not to perform tasks or animation processes based on whether or not the user will see the result.

The Intersection Observer API lets you register a callback function executed whenever an element you wish to monitor enters or exits into another element or the viewport.

The callback you register is called when:

  • The first time the observer is initially asked to watch a target element.
  • A target element intersects either the device's viewport or a specified element. That specified element is called the root element or root.

Typically, you'll want to watch for intersection changes with the element's closest scrollable ancestor or, if the element isn't a descendant of a scrollable element, the viewport. To watch for intersections relative to the root element, specify null.

const options = {
  root: null
}

Whether you're using the viewport or some other element as the root, the API works the same way, executing a callback function you provide whenever the visibility of the target element changes so that it crosses desired amounts of intersection with the root.

The intersection ratio is the degree of intersection between the target element and its root. This represents the percentage of the target element visible in the root as a value between 0.0 and 1.0.

Creating an intersection observer

Create the intersection observer by calling its constructor and passing it a callback function to be run whenever a threshold is crossed in one direction or the other:

const options = {
  root: document.querySelector('#scrollArea'),
  rootMargin: '0px',
  threshold: 1.0
}
 
const callback = (entries, observer) => {
  entries.forEach(entry => {
    // do something
  })
}
 
const observer = new IntersectionObserver(callback, options)

A threshold of 1.0 means that when 100% of the target is visible within the element specified by the root option, the callback is invoked.

Intersection observer options

The options object passed into the IntersectionObserver constructor lets you control the circumstances under which the observer's callback is invoked. It has the following fields:

root

The element is used as the viewport for checking the target's visibility. It must be the ancestor of the target. Defaults to the browser viewport if not specified or if null.

rootMargin

Margin around the root. It can have values similar to the CSS margin property, e.g., "10px 20px 30px 40px" (top, right, bottom, left). The values can be percentages or pixels. This set of values serves to grow or shrink each side of the root element's bounding box before computing intersections—defaults to all zeros.

By default, the bounding box is the same size as the root element; a positive rootMargin will make the bounding box bigger than the root element’s size, and a negative rootMargin will make the bounding box shrink smaller than the root element.

threshold

A single number or an array of numbers indicates the percentage of the target's visibility the observer's callback should be executed.

  • The default is 0 (meaning as soon as even one pixel is visible, the callback will be run).
  • A value of 1.0 means that the threshold is only considered passed once every pixel is visible.
  • If you only want to detect when visibility passes the 50% mark, you can use a value of 0.5.
  • If you want the callback to run every time visibility passes another 25%, you will specify the array [0, 0.25, 0.5, 0.75, 1].

Targeting an element to be observed

Once you have created the observer, you need to give it a target element to watch:

const target = document.querySelector('#target);
observer.observe(target);

The callback receives a list of IntersectionObserverEntry objects and the observer object itself:

const callback = (entries, observer) => {
  entries.forEach(entry => {
    // Each entry describes a change for one observed target
    entry.boundingClientRect
    entry.intersectionRatio
    entry.intersectionRect
    entry.isIntersecting
    entry.rootBounds
    entry.target
    entry.time
  })
}

Check the value of the isIntersecting property to see if the entry represents an element that currently intersects with the root.

intersectionCallback(entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      let elem = entry.target;
 
      if (entry.intersectionRatio >= 0.75) {
        intersectionCounter++;
      }
    }
  })
}

Intersection Observer Methods

  • IntersectionObserver.disconnect()
    Stops the IntersectionObserver object from observing any target.

  • IntersectionObserver.observe(target)
    Tells the IntersectionObserver to start observing a particular target element.

  • IntersectionObserver.takeRecords()
    Returns an array of IntersectionObserverEntry objects for all observed targets.

  • IntersectionObserver.unobserve(target)
    Tells the IntersectionObserver to stop observing a particular target element.

Summary

That's a wrap folks. You can easily implement infinite scrolling, lazy loading or animations using the IntersectionObserver API.

Resources

Here are some of the resources that inspired this note:

Documentation