Asynchronous Image Loading in JavaScript

June 20, 2020

If you're are using more photos on your page then most of the time you may face a large loading time, more data loss. To reduce this time you need to load images asynchronously.

Most of the image-based sites like Unsplash , Pexels load only limited images , once you scroll down they load the remaining images. So that the page loads faster and the user doesn't feel the delay on page load

Lazy Loading

Lazy loading means loading the images only when needed . Consider you have an image that is below the visible viewport of the web page, then you need not to load the image. In that case we stop loading that image , and we render image only that image element is visible in the viewport.

Using loading attribute of image

<img src='myimg.jpg' loading='lazy'>

When you set lazy as value to loading attribute , then the off-screen images are not loaded until the user scrolls to the element.

Value for loading attribute

value description
lazy Will not load image until user scrolls near the element
eager Load the resource immediately, regardless of where it's located on the page.
auto Default lazy-loading behavior of the browser, which is the same as not including the attribute.

Checking if the Browser supports loading attribute

if ('loading' in HTMLImageElement.prototype) {
  console.log("supported");
} else {
  console.log("use Intersection observer or polyfill");
}

Using Intersection Observer

Use this method only to the images which are not visible in the viewport on loading the page.

<img data-src="unicorn.jpg" alt="" loading="lazy" class="lazyload">
<img data-src="cats.jpg" alt="" loading="lazy" class="lazyload">
<img data-src="dogs.jpg" alt="" loading="lazy" class="lazyload">

Here you will set the src of the image in other data-src.

  • If the loading is available then loading attribute as lazy & set src.
  • If the loading is not supported then add Intersection Observer to it. The Intersection observer will set the src attribute once the user scrolls near the image
function imageObserver(entries, self) {
    entries.forEach(entry => { // loop all images
        if(entry.isIntersecting) { // filtering visible image
            let img = entry.target;
            img.src = img.dataset.src;
            this.unobserve(img); // remove observer
        }
    });
}

const images = document.querySelectorAll("img.lazyload");
if ('loading' in HTMLImageElement.prototype) {
  images.forEach(img => {
      img.src = img.dataset.src;
  });
} else {
    let observer = new IntersectionObserver(imageObserver);
    images.forEach(img => {
        observer.observe(img);
    });
}

Other workaround is using a small resolution image

You can first load low-resolution image then load the high-resolution image asynchronously.

<img src="unicorn-low.jpg" data-src="unicorn.jpg" class="lazyload">

In JavaScript, we add an observer in that we will load the image and replace the low-resolution image

function imageObserver(entries, self) {
    entries.forEach(entry => { // loop all images
        if(entry.isIntersecting) { // filtering visible image
            let newImg = new Image();

            let oldImg = entry.target;
            newImg.onload = function(){
                oldImg.replaceWith(newImg); // replace old image with new high resolution image
            };
            newImg.src = oldImg.dataset.src;

            this.unobserve(oldImg); // remove observer
        }
    });
}

let observer = new IntersectionObserver(imageObserver);
const images = document.querySelectorAll("img.lazyload");
images.forEach(img => {
    observer.observe(img); // in observer fn the src will be replaced with high
});

Available Libraries for lazy loading


Please donate here, for making more tutorials.