An introduction to the IntersectionObserver API
Tags: webdev, javascript, react, tutorial
Published: 2024-03-27 01:36:34

In our previous post, we discussed a few ways to [check](https://phuoc.ng/collection/intersection-observer-with-react/check-if-an-element-is-visible-in-the-viewport/) if an element is visible. We checked if it was within the window or its scrollable container, and even used the scroll event to track its visibility as users scrolled.

However, these methods had some drawbacks. In this post, we'll introduce a modern and efficient way to achieve the same functionality: the **IntersectionObserver API**.

This powerful tool allows developers to detect when an element enters or exits the viewport with ease. It's critical for optimizing website performance, as it enables lazy loading of images and other assets only when they're needed. This reduces page load times and improves the overall user experience.

## Why you should use the IntersectionObserver API

The IntersectionObserver API is a game changer for web developers. It provides a simple and efficient way to implement lazy loading and other performance optimizations that can significantly reduce load times and improve the overall user experience.

In this series, we'll explore practical examples of how to use IntersectionObserver in the real world. By minimizing the amount of data that is loaded on a page, you can make your website faster and more efficient.

But that's not all. IntersectionObserver is highly customizable and can trigger a wide range of actions based on the intersection of an element with the viewport. This allows developers to create highly dynamic and interactive webpages that are tailored to the specific needs of their users.

## IntersectionObserver browser compatibility

Good news! Most modern browsers support IntersectionObserver. According to caniuse.com, as of the time of writing, all major desktop browsers, including Chrome, Firefox, Safari, Edge, and Opera, support IntersectionObserver. On mobile devices, iOS Safari supports IntersectionObserver from version 12.2 onwards, while Android Chrome has supported it since version 51.

However, if you need to support older browsers like Internet Explorer or Safari 11 and below, you'll need to use a polyfill like [intersection-observer](https://github.com/GoogleChromeLabs/intersection-observer). This polyfill provides the same functionality as the native API and can be easily installed via npm or included in your project through a CDN.

> It's always a good idea to check the compatibility of any new feature or API before using it in production. You can use websites like caniuse.com or MDN web docs to learn more about browser compatibility and implementation details.

## Understanding the IntersectionObserver API syntax

The IntersectionObserver API works by creating an observer object that is tied to a specific element on the page. This observer watches for changes in the intersection of the target element with the viewport and triggers a callback function whenever a change is detected.

Developers can use the callback function to perform a variety of actions, such as loading additional content, animating an element, or updating a progress bar. This allows for a seamless and engaging user experience.

To create an observer, we use the `IntersectionObserver` constructor. This constructor requires two arguments: a callback function and an options object. The callback function is called each time the target element intersects with the root element or with itself. The options object lets us specify additional configurations for the observer, such as the root element to observe and the threshold at which to trigger the callback.

Here's an example of how to create a new IntersectionObserver:

```js
const observer = new IntersectionObserver(callbackFunction, optionsObject);
```

After creating our observer object, we can observe elements on our page by using the `observe()` method and passing the target element as an argument. Here's an example:

```js
const targetElement = document.querySelector('#my-element');
observer.observe(targetElement);
```

This code will tell the observer to watch for any changes in the intersection of `#my-element`. When a change is detected, our `callbackFunction` will be triggered with information about the intersection ratio between the target element and its root element, as well as the visibility of the target element.

## The options object in IntersectionObserver

When using the `IntersectionObserver` constructor, you can pass in an options object to configure the observer. The options object has several properties that can be set:

-   **root** specifies the element that serves as the viewport for checking the visibility of the target element. By default, this is set to the browser window.
-   **rootMargin** allows you to specify a margin around the root element, effectively expanding or shrinking its dimensions. This is useful when you want to detect when an element enters or exits a certain area around the viewport.
-   **threshold** sets a single number or an array of numbers between 0 and 1 that determine when the observer triggers a callback. For example, if we set `threshold: 0.5`, then our callback function will be called whenever at least half of the target element is visible in the viewport.

Here's an example of how you can create an observer with custom options:

```js
const options = {
    root: null,
    rootMargin: '0px',
    threshold: [0, 0.25, 0.5, 0.75, 1],
};

const observer = new IntersectionObserver(callbackFunction, options);
```

This observer will trigger our callback function at five different thresholds - when the target element enters and exits full view (`threshold: 1` and `threshold:0`), as well as at quarter (`threshold: 0.25`), half (`threshold: 0.5`) and three quarters (`threshold: 0.75`) visibility in relation to its parent container.

### The root property

The `root` property is a powerful option of IntersectionObserver that allows you to specify which element should be used as the viewport for checking the visibility of a target element. By default, if no value is set for `root`, it will use the browser window.

Let's say we have a `div` with a class of `.section` containing a lot of images and content that we want to lazy load as the user scrolls down the page. However, we only want to start loading this content when it enters into view within another specific `div` with a class of `.container`.

To achieve this, we can create an observer with a custom root element. Here's how it works:

```js
const observer = new IntersectionObserver(callbackFunction, {
    root: document.querySelector('.container'),
});
```

By specifying `.container` as the viewport instead of the browser window, the observer will only trigger our callback function once our target elements inside `.section` enter into `.container`. This optimization technique helps us to load only what is necessary at any given moment and avoid overloading resources unnecessarily.

### The rootMargin property

The `rootMargin` property in the options object of an IntersectionObserver lets you set a margin around the root element. This is super useful when you want to detect when an element enters or exits a certain area around the viewport.

For instance, let's say we have a hero section that fills the entire screen, and we want to trigger an animation when the user scrolls down 50 pixels from the top of this section. We can make it happen by creating an observer with a `rootMargin` value of `-50px`.

```js
const observer = new IntersectionObserver(callbackFunction, {
    rootMargin: '-50px',
});
```

Now, whenever the element we want to target intersects with the viewport within 50 pixels of its edge, our callback function will be triggered.

The `rootMargin` value is set as a string and can contain up to four space-separated values representing the top, right, bottom, and left margins, respectively. Here's an example:

```js
const observer = new IntersectionObserver(callbackFunction, {
    rootMargin: '20px 0px -50px 10px',
});
```

In this example, we set some margins for our root element. We created a margin of 20 pixels on the top, no margin on the right, a margin of -50 pixels on the bottom, and a margin of 10 pixels on the left.

If we use negative values for `rootMargin`, we can actually shrink the size of our viewport area. This is handy when we want to detect elements that are only partially visible at the edge of our viewport.

It's important to note that if you don't set any value for `rootMargin`, it will default to `0px`. So if you don't need any extra margin around your root element, you can just skip this option.

## Understanding the callback function

The callback function is the heart of the IntersectionObserver API. It is what makes everything work. It is passed as the first argument to the `IntersectionObserver` constructor, and it gets called every time the target element intersects with the root element or with itself, as specified in the observer's options object.

When our target element is visible in the viewport, the callback function gets triggered, allowing us to perform a variety of actions such as loading additional content, animating an element, or updating a progress bar.

The callback function takes two arguments: an array of `IntersectionObserverEntry` objects and a reference to the observer itself. The `IntersectionObserverEntry` object contains information about the intersection between the target element and its root element, including properties such as `isIntersecting`, `intersectionRatio`, and `boundingClientRect`.

Here's an example of a basic callback function:

```js
const callbackFunction = (entries, observer) => {
    entries.forEach((entry) => {
        if (entry.isIntersecting) {
            // Do something when target element enters viewport
        } else {
            // Do something when target element exits viewport
        }
    });
};
```

In this example, we use an arrow function to go through each entry in the `entries` array. We check if each entry is intersecting or not by using its `isIntersecting` property. If it is, we can do things like loading extra content or animating an element.

Keep in mind that we may be observing multiple elements at once with just one observer. This means our callback function will be called for each observed element whenever their intersection status changes. Therefore, we need to check which element specifically triggered our callback before doing anything to it.

## Understanding the IntersectionObserverEntry object

The `IntersectionObserverEntry` object is a treasure trove of information about the intersection between our target element and its root element. It provides us with several properties that are essential for retrieving this information:

-   **boundingClientRect** gives us the position and dimensions of our target element relative to its parent container.
-   **intersectionRatio** provides us with the ratio of intersection area to the bounding box area of either the target or root element.
-   **intersectionRect** gives us the rectangle that describes the area of intersection between our target and root elements.
-   **isIntersecting** is a simple boolean value indicating whether or not our target element is intersecting with our root element.
-   **rootBounds** provides us with the position and dimensions of our root element relative to its parent container.

These properties are incredibly useful for a variety of calculations and checks when observing our elements. For example, we can use them to calculate how much of an image is visible in relation to its parent container, or to check if an element has fully entered or exited a specific viewport.

Here's an example of how we can use these properties:

```js
const callbackFunction = (entries, observer) => {
    entries.forEach((entry) => {
        if (entry.isIntersecting) {
            console.log(`Element ${entry.target.id} is ${entry.intersectionRatio * 100}% visible`);
        } else {
            console.log(`Element ${entry.target.id} is not visible`);
        }
    });
};
```

In this example, we're using the `intersectionRatio` property to figure out how much of each element we're observing is currently visible within its parent container. This helps us perform certain actions, like loading more content or animating an element.

## Disconnecting the observer

After we're done watching an element, we can disconnect the observer by calling its `disconnect()` method. This will stop the observer from monitoring any further changes to the target element.

Here's an example of how to disconnect an observer:

```js
const targetElement = document.querySelector('#my-element');
observer.observe(targetElement);

// Some time later ...
observer.disconnect();
```

To prevent the observer from watching for changes in the intersection of `#my-element`, simply disconnect it. It's important to keep in mind that once an observer is disconnected, it can't be used again. So, if you need to start watching a new element, you'll have to create a new observer object.

Disconnecting the observer when it's no longer needed is a good practice to follow. It helps conserve system resources and ensures that your code runs efficiently.

## Conclusion

In conclusion, the IntersectionObserver API is a game-changer for web developers who want to boost performance and enhance user experience. By detecting when elements enter or exit the viewport, we can load content lazily, animate elements on scroll, and perform other actions that speed up websites and make them more efficient.

Thanks to its customizable options and callback function, the IntersectionObserver API gives developers the flexibility they need to monitor multiple elements at once and respond to changes in their intersection status. Plus, using properties like `root`, `rootMargin`, `threshold`, and `IntersectionObserverEntry`, we can tailor our observers to fit specific use cases and get even better results.

Overall, if you're serious about creating a website that's more responsive, loads content faster, and runs more efficiently, then mastering the IntersectionObserver API is a must. With its ability to track element visibility on-the-fly, this API has become an essential part of modern web development practices.

---

If you want more helpful content like this, feel free to follow me:

-   [DEV](https://dev.to/phuocng)
-   [GitHub](https://github.com/phuocng)
-   [Website](https://phuoc.ng)