DEV Community

Raymon Schouwenaar for Mr Frontend

Posted on • Originally published at Medium on

Lazy-loading images with the Intersection Observer

Last year I tried to focus more and more on the performance of websites and web-apps. It’s quite a challenge in the world of all the big frameworks and libraries.

Hopefully, you know that loading images can cost even more time and data. But lucky that we are, there is a technique called lazy-loading images.

Loading images

Loading images with the normal <img> tag, let’s the browser wait for being ready until all of the images are loaded.

<img src="" src="This is my awesome image" />

Especially if you have a website with a lot of images, it can take maybe up till 10 seconds before the user has an interactive page.

On mobile connections that can even worse. With bad or slow connections your user sometimes will wait for tens of seconds or even minutes.

But we all know that those users want to wait that long! They will leave after a few seconds!

Lazy-loading images

We want our pages loaded as fast as it is possible. Our goal should be 0–5 seconds, this is the amount of time a user will be patient for the page to load.

So if we avoid the normal <img src="url" />, so our pages will load a lot faster.

If we use a data-attribute to put in the url of the image, we can put it in the src attribute to load them when it’s in the viewport.

Most developers will use libraries for lazy-loading that are using a eventListener on the scroll event to check if a element is in the viewport. But we need something better since the eventListener on the scroll is kind of heavy on some devices!

Intersection Observer

The Intersection Observer does a pretty good job in detecting if a certain element is inside the visible part of your browser.

The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document’s viewport. source: Mozilla Docs

With this API we can listen of a element is in the viewport if they intersecting with the viewport.

Configure the Intersection Observer

We have a few options in configuring the observer.

const options = { root: document.querySelector('#container'), rootMargin: '0px', threshold: 1.0 }

const observer = new IntersectionObserver(callback, options);


In the root property define the element that will be set as the viewport. Keep in mind that if you target a element (or the body) and when its height is on auto, than all the elements will be set on visible. So if you set a element, set a height that is not auto, otherwise it won’t work like expected. If you don’t define this property it will automatically use the browser viewport.


If the rootMargin value is set to 0, it will not look outside the root element. If you put in 10px, it will check earlier if a element is scrolled into it’s rootelement.


If this value of threshold is 0, it will run the callback when 1px of the element is inside the root element. When the value is 1.0 it will be trigger the callback when it’s 100% inside the root element. But if you want the callback to be called when the element is 50% inside the root element, put in the value 0.5.

Target elements

In order to use the Intersection Observer, we need element to observe some elements!

The elements we want to observe have a classname ‘fake-image’, and we are gonna loop through the elements to start an observer for every fake-image element.

We also want to make sure, that when the element is in our root element, that the observation will be stopped. This saves some power on your computer or device.

const io = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.intersectionRatio > 0) { io.unobserve(; } }); }, options);

const targetElements = document.querySelector('.fake-image'); for(let element of targetElements) { io.observe(element); }


Let’s try this out!

I’ve created a small demo to show you that Intersection Observer works really smooth.

Start scrolling, everytime a element is 100% visible it will turn green and the text will be “Loaded!”.

Browser support

The support for the intersection observer is pretty nice. It’s implemented in Chrome (desktop & mobile), Firefox, Edge, Android browser. So it’s missing in IE11 and Safari (desktop & mobile). The webkit team is working hard on it because it’s marked as “In Development” 👍, so hopefully it will soon be supported in Safari (March 2018).

To support the browsers that don’t support this cool API (yet) we can use a polyfill. We can get it via npm: intersection-observer npm install intersection-observer --save.

Let’s build lazy loaded images

Now we know how we can use the intersection observer, we’re gonna make our images load asynchronous into our browser when they are in the visible viewport.

At the beginning of this blogpost I’ve showed you how most of the lazy load functionality is build last years. So let’s replace the scroll event listener for the intersection observer.


If you remember the HTML we used in the example before then you see we only need to add a <img> tag with the data-attribute data-src.

A data-attribute is a perfect solution to put in the url, so we can put the url in their.

<div class="fake-image"> <h2>Fake image</h2> <img data-src="" alt="" /> </div>


For the JavaScript we only need one function to make our image load. Call the function inside the intersection observer.

const io = new IntersectionObserver(entries => { entries.forEach(entry => { // console.log('entry: ', entry); if (entry.intersectionRatio > 0.9) { loadImage(;'active'); // = '<h2>Loaded!</h2>'; io.unobserve(; } }); }, options); function loadImage(imageElement) { setTimeout(() => { console.dir(imageElement.querySelector('img')); imageElement.querySelector('img').src = imageElement.querySelector('img').dataset.src; }, 1000); }

The only thing we need to do in the function is, putting the url from the data-src attribute into the src attribute.

When the src attribute will be present, the image will load in the browser.

In the JavaScript code I’ve put in a timeout for 1 sec to see the loading happen.


Let’s checkout the example. Scroll the view with images down so you can see for yourself that it works.


If you learned something or have other ways to loop over a NodeList Object from the querySelectorAll, please let me know on twitter 😉: @rsschouwenaar

Originally published on

Top comments (2)

itsjzt profile image
Saurabh Sharma

Great article. Intersection Observer is so cool.

Btw why you put so many \ in your code examples

rsschouwenaar profile image
Raymon Schouwenaar

Thanks man! I think there is something wrong yes, gonna fix it! Thanks for letting me know!