Exploring the Basis of the IntersectionObserver API
Published: 2024-05-16 17:23:10

The web continues to evolve and features such as infinite scrolling and lazy loading images are no longer new to us. These features improve the user experience by dynamically loading content as needed. But how do we implement these features efficiently?

## Introduction
Historically, determining the visibility of an element, or the relative visibility of two elements, has been a challenging task. Traditional methods were often unreliable and could slow down both the browser and web pages. Implementing intersection detection in the past involved using event handlers and loops, frequently calling methods such as `Element.getBoundingClientRect()`. This approach could lead to significant performance issues, especially on complex or content-heavy pages.

## How Does IntersectionObserver Work?
The IntersectionObserver API simplifies the process of detecting visibility changes. It allows you to asynchronously observe changes in the intersection of a target element with a parent element or the viewport. This means you can easily detect when an element enters or leaves the viewport or any other element you specify as the root.

Implementing the observer requires 2 steps. One is to provide the element we want to monitor and the second is to set up the observer with a so-called configuration object.

The following code illustrate a very basic implementation

```js
const tagetElement= document.querySelector('#load-more');
const observerCallback = (entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      //perform code on intersection
    }
  });
};

const observer = new IntersectionObserver(observerCallback , {
  root: null, // Root element, null means the browser viewport
  rootMargin: '0px',
  threshold: 1.0 // Trigger the callback when 100% of the target is visible
});

// Start observing the target element
observer.observe(tagetElement);
```
**Terminology :**

- **Target Element**: The element you want to observe.
- **Root Element**: The element that is used as the viewport for checking the visibility of the target. If not specified, the browser’s viewport is used by default.
- **Threshold**: A single number or an array of numbers indicating at what percentage of the target's visibility the observer's callback should be executed.
- **Root Margin**: An offset margin applied to the root’s bounding box, affecting when the observer's callback is triggered.
- **Observer callback** : When the intersection of the target element crosses the specified threshold, the callback function is executed. This callback provides entries, which are objects containing information about each observed target element and its intersection status.

## Quick Example with a load more on Scroll Implementation
Implementing infinite scrolling with IntersectionObserver is simple and efficient. In the following snippet I will share html css and javascript code that can be used together to implement the load more on scroll feature.

```css
body {
    padding: 20px 40px;
    margin: 0px auto;
    max-width: 1440px;
    background-color: bisque;
}

.main-content {
    display: flex;
    flex-direction: column;
    gap: 20px;
    width: 100%;
    justify-content: center;
    align-items: center;
    min-height: 110vh;
}

.content {
    height: 90vh;
    width: 70vw;
    display: grid;
    place-content: center;
    background-color: rgb(240, 240, 240);
}
```

```html
<div class="main-content">
    <!-- Initial content that will be displayed -->
    <div class="content">Content #1 (initial)</div>
</div>
<!-- Load more trigger element -->
<div id="load-more"></div>

<!-- we use jQuery to manipulate the DOM easily -->
<script src="https://code.jquery.com/jquery-3.7.1.slim.js"></script>
```

```js
// Select the element that will trigger loading more content
const loadMoreTrigger = document.querySelector('#load-more');
const content = ["Content #2", "Content #3", "You are done!"];
let index = 0; // This can be used to fetch data in chunks using pagination

const loadMoreCallback = (entries, observer) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            if (content[index]) {
                loadMoreContent();
            } else {
                // Stop observing the trigger element if all content has been loaded
                observer.unobserve(entry.target);
            }
        }
    });
};

const observer = new IntersectionObserver(loadMoreCallback, {
    root: null, // Use the browser's viewport as the root
    rootMargin: '0px', // No margin around the root
    threshold: 1.0 // Trigger the callback when 100% of the target is visible
});

observer.observe(loadMoreTrigger);

const loadMoreContent = () => {
    $('.main-content').append(`<div class="content">${content[index]}</div>`);
    index++; // Increment the index to load the next piece of content
};
```
This example shows how to use IntersectionObserver to implement infinite scrolling. When the user scrolls to the #load-more element, the callback function loads more content, the load-more is pushed almost down, and when the user scrolls again we load data and so on.

If the content is larger, we can say that we have created a so-called infinite scroll effect.

Please access a [fully working demo here](https://codepen.io/elvisans/pen/YzbyerQ)

## Conclusion and Next Steps
The IntersectionObserver API provides a robust, efficient way to handle visibility changes on your web pages. Using this API, you can implement features such as infinite scrolling and lazy image loading without compromising performance. It removes the need for performance-draining event listeners and provides a simpler, more maintainable solution.

Next steps could include exploring more advanced use cases for IntersectionObserver, such as triggering animations, monitoring ad visibility, or implementing intelligent preloading strategies. By mastering this API, you can significantly improve the user experience on your websites and web applications.