Lazy Loading Images with Intersection Observer

This post is over six (6) months old. Some things on this page my be out of date or no longer applicable.

IntersectionObserver is a fairly new web API that makes lazy loading images extremely easy. How easy is to to lazy load with IntersectionObserver? You can do it in about ≈20 lines of code if you don't need a polyfill. Yes 20. You'll need a polyfill though so that selling point is kinda .... meh haha.

Update! Introducing IOLazy Library

I turned this post into a small JavaScript library to lazy load images. You can find the demo for the library over at IOlazy Demo page  

IOLazy File Sizes
File Size in KB Gzip Size
iolazy.min.js ≈1.6KB ≈0.846kB
using polyfill for intersection observer ≈6.3KB ≈1.93KB
Totals   ≈2.776KB

Browser Support

As of this post being published Edge 15 (released 11 April 2017 with Windows 10 Creators Update), Chrome 51 and Opera 38 support it without a polyfill. Firefox has this enabled behind a flag.

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

IntersectionObserver Javascript

As I mentioned above using the IntersectionObserver API to lazy load images results in a small bit of Javascript. IntersectionObservers basically tell the browser "don't load this until it comes into or around the browsers viewport". If this is faster than the other lazy load solutions I don't know I haven't tested it out so you'll have to test this yourself to see.

I've taken the example from Quick Introduction to the Intersection Observer API   and made a few changes.

Many of the tutorials from 2016 haven't updated their examples to an API change that check for .isIntersecting

That also introduces an issue in Microsoft Edge 15 since it doesn't have an .isIntersecting property. Nolan Lawson wrote a another small polyfill we can place in our intersectionobserver lazy load file. You can find that here at IntersectionObserver issue #211 comment by Nolan Lawson

We'll follow this pattern to create our Observer. Note I'm using es6 so you may need a build step/compilation step based on your browser support.

  1. set a variable with a node list of all our image elements we want to lazy load
  2. Create a new IntersectionObserver, add a callback and options to the new IntersectionObserver
  3. get the data-src attribute from all our lazyload image candidates in our callback
  4. If our image is within our Threshold   we'll apply our image candidate from #3 to our image src attribute.
// small polyfill for Microsoft Edge 15 isIntersecting property
// see
if ('IntersectionObserver' in window &&
  'IntersectionObserverEntry' in window &&
  'intersectionRatio' in window.IntersectionObserverEntry.prototype &&
  !('isIntersecting' in IntersectionObserverEntry.prototype)) {

  Object.defineProperty(window.IntersectionObserverEntry.prototype, 'isIntersecting', {
    get: function () {
      return this.intersectionRatio > 0

// #1 using the spread operator get all our images with the class 'lazyload'
const imgElements = [ ...document.querySelectorAll( '.lazyload' ) ];

// #2 create our new IntersectionObserver followed by our callback and any options
// 'onChange' is our callback. 'threshold' is our options - the percentage our image is visible.
let observer = new IntersectionObserver( onChange, {
  threshold: [ .15 ],

// #3 Our callback to grab all our data-src attributes when they become visible.
// This comes from our threshold set above in our options
function onChange( changes ) {
  // for each element that becomes visible
  changes.forEach( change => {
    // Here is the API change mentioned in the linked tweet above
    if ( change.isIntersecting ) {
      // added a class to our 'target'. This is the visible image element from #1 'visible' );
      // the visible image element we'll grab our image to load from the data-src attribute
      // and apply it to the src attribute =;
      // stop observing this element
      observer.unobserve( );

// Here is where we'll 'observe' our elements to lazyload.
const createObserver = function () {
  // for each of our image elements
  imgElements.forEach( img => {
    // observe the image currently visible and apply #3
    observer.observe( img );

// We'll listen for the 'load' event. Once that has fired we'll apply the function we created
// above called 'createObserver'.
window.addEventListener("load", () => {
}, false);

Lazyload HTML markup

The HTML markup needed is rather small too. We'll need to add in a data-src attribute with the image we'll load once it comes into our threshold defined in our Intersection Observer.

<img class="lazyload" data-src="/your/file.jpg" src=""  alt="an awesome bear" >

Making Sure Images Load

If your image tag has an empty src attribute and there is no alt text then FireFox will not load the image.

Easiest fix for this is to have Alt text. In cases you are using a figure element with figcaption that negates the use of alt text then you should use a base64 encoded 1px gif - or a really small low quality placeholder image.

Polyfills for IntersectionObserver

Now on to getting this to work in browsers with no support yet.

That above one is nice if you have have one off's or you have a build step, use NPM with Browserfiy or Wepback.

The easiest one to use would be the one from That one is just inserting a script tag into your document with the features query string set to IntersectionObserver

Check out! It could really simplify your polyfill needs!

Live Demo

There is a demo located here.

There willl be some lorem ipsum text followed by an image that will lazy load.

You can open up your console and see the console logs I've placed in the script to tell you when an image is visible and when the visible class was applied.

More Information