Lazy loading images using Intersection Observer

If you haven’t had the chance to watch it yet, Paul Lewis put together an awesome video series that demonstrates how to build a media player alongside some of the great features of Progressive Web Apps. There are a series of videos on YouTube that take place over a couple of days as he builds each new part of the site. Watching videos of developers showcasing their work is definitely one of my favourite ways of learning new things!

Intersection Observer - Paul Lewis

As he was demoing his code, I noticed that he created an awesome little helper library to lazy load images. I am always looking for ways to improve the performance of my websites, so this definitely sparked my interest.

What’s the big deal with lazy loading?

The idea behind lazy loading images is that you wait until a user scrolls further down the page and the image comes into view before making the network request for it. If your web page contains multiple images, but you only load each image as they are scrolled into view, you’ll end up saving bandwidth as well as ensuring that your web page loads quicker.

To give you an idea of this in action, let’s imagine the following page with three images on it.

Intersection Observer

If the user lands on the page and only views the first image, we don’t want to load the image of the pizza at the bottom of the page until the user scrolls down and the image is actually in view. If we lazy load the image instead, it means that the user only downloads exactly what they need, when they need it, making your web page a lot leaner.

For the more experienced developer, you might be familiar with lazy loading images, after all this concept has been around for a while. So what’s new?! There are many lazy loading libraries out there at the moment that do great job. I have even previously written about one on this blog (many years ago). The problem is, almost all of these libraries tap into the scroll event or use a periodic timer that checks the bounding on an element before determining if it is in view. The problem with this approach is that it forces the browser to re-layout the entire page and in certain conditions will introduce considerable jank to your website. We can do better!

Intersection Observer to the rescue!

This is where Intersection Observer comes in. Intersection Observer is built into most modern browsers and lets you know when an observed element enters or exits the browser’s viewport. This makes it ideal because it is able to deliver data asynchronously and won’t impact the main thread, making it an efficient means of giving you feedback.

In Paul’s example, he shows how to use Intersection Observer to lazy load images when they come into viewport. I’ve taken his initial code and and tweaked it slightly to make it easier for me to understand. In this article, I am going to run through the basics of Intersection Observer and show you how you can start lazy loading images in a super efficient way.

Getting Started

Imagine a basic HTML page with three images similar to the one above. On the web page, you’ll have image elements that are similar to the code below:

<img class="js-lazy-image" data-src="burger.png">

You may notice that in the code above, the image file has no src attribute. This is because it is using a data attribute called data-src that points to the image source. We will use this to load the images when they come into viewport. You may also notice that the image element also has a class called “js-lazy-image” - we will be using this shortly in our JavaScript code in order to determine which elements we want to lazy load.

Next, we need to create the code that is going to lazy load the images on the page. Inside our JavaScript file that will be included on the page, we need to create a new Intersection Observer.

The example above looks like a lot of code, but let’s break it down step by step. Firstly, I am selecting all of the images on the page that have the class “js-lazy-image”. Next, I’m creating a new IntersectionObserver and using it to observe all of the images that we have selected that have the class “js-lazy-image”. Using the default options for IntersectionObserver, your callback will be called both when the element comes partially into view and when it completely leaves the viewport. In this case, I am passing through a few extra configuration options to the IntersectionObserver. Using a rootMargin allows you to specify the margins for the root, effectively allowing you to either grow or shrink the area used for intersections. We want to ensure that if the image gets within 50px in the Y axis, we will start the download.

Now that we’ve created an Intersection Observer and are observing the images on the page, we can tap into the intersection event which will be fired when the element comes into view.

In the code above, whenever an element that we are observing comes into the users viewport, the onIntersection function will be triggered. At this point we can then loop through the images that we are observing and determine which one is in viewport. If the current element is in the intersection ratio, we know that the image is in the users viewport and we can load it. Once the image is loaded, we don’t need to observe it any more, and using unobserve() will remove it from the list of entries in the Intersection Observer.

That’s it! As soon as a user scrolls and the image comes into view, the appropriate image will be loaded. The best thing about this code is that Intersection Observer keeps it smoother than Barry White. I’ve tried to keep the code above as succinct as possible, but if you’d like to see the full version, I’ve created a Github repo with a working example of this in action.

Browser Support

At this point, you might be wondering about browser support for this feature. Intersection Observer is currently supported in Edge, Firefox, Chrome, and Opera which is great news.

However, in order to ensure that our code doesn’t break for browsers that don’t support this feature, we can use feature detection to determine how we want to load the images. Let’s have a look at the code below.

In the code above, we are checking to see if IntersectionObserver is available in the current browser and if it isn’t we simply load the images immediately, otherwise we use our default behaviour.

If you really like the ease of the Intersection Observer API and would like to use a polyfill instead, the WICG have created one that is available on their Github repository. The only downside of this is that you won’t get the performance benefits that the native implementation would give you.

You could even take this a step further and add support for users that don’t have JavaScript enabled as suggested by Robin Osborne.

Summary

In this article, we’ve used Intersection Observer to lazy load images, but you can use it to do so much more. It could be used to determine if someone is looking at an advert or even if an element in an iFrame is in view. The easy to understand API makes it open to many options.

If you’d like to learn more about Intersection Observer, I recommend reading this informative article on the Google Developers site. I also highly recommend watching Paul Lewis’ video series on Youtube, it’s packed with great tips and you’ll definitely learn something.

Oh, and a big thanks to Paul for reviewing this article!








Comments

David - 8/8/2017
Another good article Dean! This API sum's up my experience with the Google Chrome Dev Team. I used and tested out this API way back in 2016. It worked amazing in Google Canary and with 100 images on a test page loaded in 200mS whereas the Jquery method took 2 seconds to load the same test webpage. However, when I went to caniuse.com this api only worked in Google Canary and not Firefox or any other browser and no polyfill was created last year. So I had to give up on this API. I have the same experience with many API's from Google Chrome Dev Team all same problem. I think my point is that I wish the hard working people at Google add a polyfill the same time they create these API's.

Dean Hume - 8/8/2017
Hi David - Thanks for the great feedback. I'm not sure if you managed to read the whole article, but at the moment this API is supported in most modern browsers - not just Chrome (check out http://caniuse.com/#feat=intersectionobserver). The great thing about this is that it wasn't put together by the Google team but rather the WICG, Chrome was just early to implement it. This has been rolled out as a whole standard across browsers now! Also, there is a polyfill that was created by the WICG - check it out at (https://github.com/WICG/IntersectionObserver/tree/gh-pages/polyfill). I mentioned this in the article too!

Šime Vidas - 8/8/2017
I just wrote a long comment about WICG being merely a platform for incubating proposals, and that the API in question *was* created, owned, and maintained by Google, and not by WICG (because there is no WICG team). However, my comment was lost (“Your comment was unsuccessful.”), because I didn't see the CAPTCHA below the submit button. Please consider fixing this. For example, you could store the comment text in sessionStorage, and then restore it if the submission fails.

John - 8/9/2017
Hello Dean got a small request and wondered if you would be interested in writing an article about the new Performance Metrics API's for PWA's, as I find your articles easier to understand. I will try and add a link which contains a youtube video, hopefully its useful to you: https://developers.google.com/web/updates/2017/06/user-centric-performance-metrics

pjd - 8/10/2017
Hi, This doesn't appear to work on Edge. I have already implemented my own piece of functionality with intersection observer and it also didn't work in Edge. In the end I had to write a test to avoid using it if it didn't work. I don't know why though. You have any insight here? I have Edge 15, which is supposed to support it. Peter

raglan - 8/11/2017
Nice! And thanks for including the link to the no-JS fallback. Can you now explain how this can work with responsive images? i.e srcset, sizes, picture, etc. :)

Claudio - 8/11/2017
Hi, How this method affect HTTP/2 ? If i change the src of several img, does the download of those images be done by HTTP/2 ? Thank

Scott G - 8/16/2017
Could this apply to background images as well? And if so, how could you do that since they are in css stylesheets???

Scott G - 8/16/2017
I copied your code exactly, not working in IE 11. Image doesn't load.

Denise - 9/18/2017
For anyone finding that this doesn't work in a specific browser, these tools might be of interest: https://github.com/w3c/IntersectionObserver/tree/master/polyfill http://caniuse.com

Ben Nadel - 11/5/2017
Great read. I just heard about this API on a recent podcast. I can't wait to dig in.


Add your comment

300 Characters left


Please fill this in to confirm that you are human