How I used the IntersectionObserver API to lazyload Images
Tags: frontend, webperf, javascript
Published: 2024-02-05 10:37:59

Web performance is an important aspect to consider when building web applications, as it is of necessity that users get a nice experience while using your application. In this article, I want to share my experience using the IntersectionObserver API in JS to lazy load property images on a real estate website i am building, which helped me improve the load time of the website by a significant amount. I won't dive too deep into how this API works, but I would link articles at the end that explains it in more detail.

## The issue I Had

I have been building this website [hmghomes.com](https://www.hmghomes.com/), and working on making it load faster because it loads very slowly. I realized from the network tab in chrome dev tools that one of the biggest reasons the website takes time to load is because the sizes of the property images are large.

![Image loading analysis](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3zu0a6kow9h4yz800hsu.png)

![Image loading analysis2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/501iu1rz5zr54mo52bjl.png)

I checked for possible solutions to the problem, and the most obvious one I saw was to optimize the images (of course!). I then stumbled on another solution to lazy load the images, as loading every single property image from the DB is not efficient. I wasn't sure how to go about doing this as there are multiple ways to lazy load images on a website. I was looking for the best approach for my use case, and after doing some research, I found out about the IntersectionObserver API.

At first, this API was very confusing to understand, like what am I observing?, How does intersection happen?, but thanks to this video by [webdevsimplified](https://www.youtube.com/watch?v=2IbRtjez6ag) on the topic, I was able to understand how to use it to achieve what i was looking for.

Here is a preview of how the website loaded before

![Unoptimized image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/279mwxpc1d77sofz5s3b.gif)

Here is a preview of how the website loads now

![Optimized Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uwdd8z356by344h1d96z.gif)

This was the code I wrote to get the result I wanted

```js
document.addEventListener("DOMContentLoaded", function () {
    let lazyloadImages;

    if ("IntersectionObserver" in window) {
      lazyloadImages = document.querySelectorAll(".image");

      let imageObserver = new IntersectionObserver(function (
        entries,
        observer
      ) {
        entries.forEach(function (entry) {
          if (entry.isIntersecting) {
            let image = entry.target;
            image.classList.remove(".lazy");
            image.setAttribute(
              "style",
              `background-image: url("${image.dataset.src}")`;
            );
            imageObserver.unobserve(image);
          }
        });
      });

      lazyloadImages.forEach(function (image) {
        imageObserver.observe(image);
      });
    }

      document.addEventListener("scroll", lazyload);
      window.addEventListener("resize", lazyload);
      window.addEventListener("orientationChange", lazyload);
    }
  });
```

Let me briefly explain the code,

- I first check when the DOM has fully loaded to be sure the code runs as expected

- I also checked if the IntersectionObserver API exists in the window object, because there are some browsers that don't support this

- An instance of the IntersectionObserver API class is then created, this is where the magic starts happening. The API takes in a callback function which receives 2 arguments, **entries** and **observer**, but i will only talk about the entries argument in this article. The entries are the items the intersectionObserver keeps track of that are yet to show on the DOM.

- Next, I am checking if the item is visible or almost visible to the user (that is what **entry.isIntersecting** means). If it is visible, I get the target element for that specific entry (which in this case is an image tag), after which I remove the .lazy class and add a style attribute to dynamically set a background image

- After doing all this, I called imageObserver.unobserve(image) to tell the Observer to stop tracking that specific entry. Why is this necessary? because it is already visible to the user at this point. The whole point of tracking an entry is to monitor it's position before it becomes visible to the user, once it shows, there is no point of monitoring it again. I would say that there can be cases where you might still want to observe the entry even after the intersection has been made, it all depends on what you are trying to achieve.

## Conclusion

I haven't yet pushed this live, I still need to optimize the JS and CSS files too, but this has started improving the speed of the website. Another thing I want to point out is that the IntersectionObserver API can be used for tasks other than just lazy loading, as long as it requires interacting with the users viewport. You can read more about this API from [MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API), and [This Article](https://hackernoon.com/a-beginners-guide-to-javascripts-the-intersection-observer-api-j8s32rb) by David Herbert

Have you ever used the IntersectionObserver API before?, I would like to know. You can find out more about what I do on [Twitter](https://twitter.com/kansoldev) and [Github](https://github.com/Kansoldev)