Get more comfortable with IntersectionObserver 🚦👀
Tags: webdev, javascript, tutorial, learning
Published: 2024-05-22 00:01:47

As a web developer, you've likely encountered situations where you needed to detect when an element becomes visible or hidden on the page. Maybe you wanted to lazy-load images, videos, trigger animations, or track user engagement. In the past, you might have relied on scroll events or complex calculations to achieve this. No need to overcomplicate things! The IntersectionObserver API is here to simplify your code.

## What is IntersectionObserver?

IntersectionObserver is a powerful browser API that allows you to observe changes in the intersection of a target element with an ancestor element or the viewport. In other words, it lets you know when an element enters or leaves the visible area of the page. For example, in this gif, I am using the IntersectionObserver to play and pause videos as they enter/leave the viewport.

![GIF of videos being paused from intersection observer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wjjvtyfcsp17qyef9ae5.gif)

## Basic usage

To get started with IntersectionObserver, you first need to create an instance of it. The constructor takes two parameters: a callback function and an optional configuration object.

```javascript
const observer = new IntersectionObserver(callback, options);
```

The callback function will be called whenever the observed elements intersect with the root. It receives an array of IntersectionObserverEntry objects, representing the intersection changes for each observed element.

```javascript
const callback = (entries, observer) => {
  entries.forEach(entry => {
    // Do something with the intersection entry
  });
};
```

To start observing an element, simply call the observe method on your IntersectionObserver instance, passing the target element as an argument.

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

## IntersectionObserver options

When creating an IntersectionObserver, you can customize its behavior by passing an optional configuration object. Let's take a closer look at the available options.

- **root**: The ancestor element to use as the viewport for checking visibility. By default, it's set to the browser viewport.
- **rootMargin**: A string that specifies the margins around the root. It allows you to expand or shrink the effective bounding box of the root.
- **threshold**: A number or an array of numbers between 0 and 1, representing the percentage of the target's visibility at which the callback should be triggered.

Lets take a closer look at each of these:

### Root

The **root** option specifies the ancestor element to use as the viewport for checking visibility. By default, it's set to the browser viewport. However, you can set it to a specific element to observe an intersection relative to that element's boundaries.

```javascript
const options = {
  root: document.querySelector('#header-target'),
};
```

In this example, the IntersectionObserver will observe the target element's intersection with the #header-target element instead of the browser viewport. This allows us to watch for specific elements intersecting with each other.

![Image explaining root](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jo7jzvd3d3wianu6hond.png)

### RootMargin

The **rootMargin** option allows you to specify margins around the root element's bounding box. It effectively expands or shrinks the area used for intersection calculations. The syntax is similar to the CSS **margin** property.

```javascript
const options = {
  rootMargin: '50px 0px 0px 0px',
};
// or
const options = {
  rootMargin: '500px',
};
```

![Image explaining root margin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/534y75kpeqme97zxf3km.png)

In the image above, the viewPort (which is are root) is shown as the blue line. When the rootMargin is negative, it cuts into the root (shown with the red line). When the rootMargin is positive, it extends the root (shown with the green line).

Be careful using a negative root margin. If the margin is larger is larger then the screen size, the target will never be visible. This is a larger problem on mobile devices. I would generally suggest using a higher threshold, instead of a negative margin.

### Threshold

The **threshold** option determines at what percentage of the target's visibility the callback should be triggered. It can be a single number or an array of numbers between 0 and 1.

```javascript
const options = {
  threshold: 0.5,
};
```

In this example, the callback will be triggered when 50% of the target element is visible within the root.

```javascript
const options = {
  threshold: [0, 0.25, 0.5, 0.75, 1],
};
```

Here, the callback will be triggered when the target is 0%, 25%, 50%, 75%, or 100% visible within the root.


![Image explaining threshold](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d2x9ii04h6zcbf5f12nk.png)


By adjusting these options, you can fine-tune the behavior of the IntersectionObserver to suit your specific needs. Whether you want to observe intersection relative to a particular element, expand or shrink the effective bounding box, or define custom visibility thresholds, these options provide the flexibility to do so.

## Handling intersections

When the callback function is invoked, it receives an array of IntersectionObserverEntry objects. Each entry provides information about the intersection between the target element and the root.

Some key properties of an IntersectionObserverEntry include:

- **target**: The DOM element being observed.
- **intersectionRatio**: The ratio of the intersected area to the total area of the target.
- **isIntersecting**: A boolean indicating whether the target is intersecting with the root.

```javascript
const callback = (entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      console.log(`${entry.target.id} is intersecting!`);
    }
  });
};
```

## Example

Here is a small codepen example, putting all of the pieces together. I encourage you to play around with the settings and watch how the behavior changes. Right now, it's set up so that 50% of the blocks need to be visible in the viewport before changing colors.

{% codepen https://codepen.io/matt-lewandowski/pen/qBGZgzb %}

## Performance considerations

One of the great advantages of IntersectionObserver is its performance. Unlike listening for scroll events and manually calculating element positions, IntersectionObserver is optimized to minimize the performance impact on your web page.

IntersectionObserver is widely supported in modern browsers, but it's always a good idea to check for browser compatibility and provide fallback mechanisms when necessary.

## Advanced use cases

IntersectionObserver opens up a world of possibilities for creating engaging and performant web experiences. Some common use cases include:

- Lazy-loading images or other content as the user scrolls
- Triggering animations when elements come into view
- Implementing infinite scrolling or pagination
- Tracking user engagement and analytics

## Conclusion

IntersectionObserver is a must for detecting element visibility on the web. By leveraging its capabilities, you can create more efficient and user-friendly experiences without the headaches of manual calculations and event listeners.

So go ahead, give IntersectionObserver a try in your next project!

Oh and one last shameless plug 😁
If you work in an agile dev team that has sprint retrospectives, check out my [Free Sprint Retrospective App](https://kollabe.com/retrospectives) called Kollabe!