Lazy Loading Images with the Intersection Observer API

Since writing this article there have been some advancements in lazy loading:

Native lazy loading is starting to be implemented in browsers. As support increases this will be the best way to handle lazy loading. Techniques like outlined in this article can be a good fallback for browsers which do not support the new loading attribute. This article shows how to use the new loading attribute and how to feature detect it.

Googlebot is now evergreen and will continue to run the latest version of Chromium. This means that SEO support for lazy loading images via the Intersection Observer API and the native loading attribute should no longer be an issue.

What is Lazy Loading?

Lazy loading is the process of not automatically loading all images on a web page but only loading them when they enter the viewport.

The benefits of lazy loading are:

  • Faster page load speed.
  • The user only has to download images they reach within the page.

Previously, determining what elements were within the viewport whilst scrolling was a fairly complex problem and came with some performance drawbacks. Now with the Intersection Observer API the problem is now a breeze and lazy loading can be implemented simply with very little code.

Implementation

The basic idea behind lazy loading is to not put the src attribute in the HTML but set it when desired using JavaScript.

A simple way to do this is to store the desired source in a data attribute and then later set the src to be equal to this value in JavaScript.

<img data-src="cookies.jpg" />
const img = document.querySelector('img[data-src]');
img.src = img.dataset.src;

Using the Intersection Observer API

To determine at what scroll point to load the images we can use the Intersection Observer API. We must first create an IntersectionObserver object and pass it the thresholds for which we wish to observe intersection changes. A threshold of zero will trigger the observer’s callback immediately after a single pixel of the image enters the bounding box of the root element (by default the browser viewport). The rootMargin grows or shrinks the root element’s bounding box. In the example, image downloads will begin when the image is within 50px in the y-axis.

We then ask the observer to observe each image we wish to lazy load.

Whenever one of our observed images passes the intersection threshold the image will be loaded with the ‘load’ function.

const options = {
  root: null,
  rootMargin: '50px 0px',
  threshold: 0
};

// Create the intersection observer
const observer = new IntersectionObserver(function(entries, observer) {

  entries.forEach(function (entry) {
    if (entry.intersectionRatio > observer.thresholds[0]) {
      load(entry.target, observer);
    }
  });    
}, options);

// Observe all img tags with the 'data-src' attribute
document.querySelectorAll('img[data-src]').forEach(function(img) {
  observer.observe(img);
});

// Load an image and unobserve it
function load(img, observer) {
  if (observer) {
    img.addEventListener('load', function() { 
      observer.unobserve(img);
    });
  }

  const dataSrc = img.dataset.src;

  if (dataSrc) {
    img.setAttribute('src', dataSrc);
  }
}

JavaScript disabled?

To combat a situation where JavaScript is disabled you can include a noscript element including an img element complete with src attribute.

<img data-src="cookies.jpg" />
<noscript>
  <img src="cookies.jpg" />
</noscript>

Fallback

If the Intersection Observer API is not supported you could fallback on another lazy loading method without the API.

Alternatively, you could simply give in and load all of the images straight away! This would look like this:

document.querySelectorAll('img[data-src]').forEach(function(img) {
  load(img);
});

SEO

One aspect to carefully consider when implementing lazy loading is the negative effect this could potentially have on SEO. It is likely that Googlebot and other crawlers may not be able to retrieve and index your images for image search due to the fact they will only be loaded if the page is scrolled. How a page appears to Googlebot can be tested using Google Search Console’s ‘Fetch as Google’ function.

I would recommend to avoid using lazy loading for images which are critical to your search strategy that you wish to appear in image search results.

Support

Support for the Intersection Observer API is spotty but improving. At the time of writing it is supported in Chrome and Edge with Firefox due to follow suit shortly in a matter of days.

Can I Use intersectionobserver? Data on support for the intersectionobserver feature across the major browsers from caniuse.com.

Conclusion

With the Intersection Observer API implementing lazy loading is really simple and requires very little code. Other than SEO consideration there is now little reason I can see not to utilise lazy loading to improve performance and user experience.