Lazy Loading Images

This tutorial explains the ways to lazy load images by deferring the actual loading of offscreen images using placeholder images smaller in size as compared to the actual image. We can use the lazy loading concepts for the images which are not visible on the viewport or visible area when the page loads. These include the hidden slider slides and the images present in the content being visible when the user scrolls down the page.

The basic concept behind the lazy loading image using the image tag is as shown below. The same concept can be used for elements using the image as a background by changing the background URL property.

// Assign the placeholder image path to src to be loaded initially

// Assign the default value to data-src attribute

// Assign the comma separated possible path to data-srcset attribute

<img class="lazy-img" src="temp-image-1.jpg" data-src="image-1-1x.jpg" data-srcset="image-1-3x.jpg 3x, image-1-2x.jpg 2x, image-1-1x.jpg 1x" alt="Describe your Image" />

If you are not actually concerned about the space utilized by the image to maintain consistency, the initial value of src attribute can be ignored without using any placeholder image. We can also set the min height property of the lazy loading images to occupy some space.

The ways to load the actual image are as explained below. We can load the actual image by JavaScript once the image tag appears in the viewport area using the image actual path from data-src and data-srcset values.

We will be using the Wind Power Royalty Free Stock Photo from Pixabay and Silhouette Of People by Bayu jefri for demonstration purpose. The size of both the placeholder and actual images are as mentioned below.

Wind Power - wind-power-1x.jpg(960x640, 44.9 kB), wind-power-2x.jpg(1920x1280, 230 kB), wind-power-temp.jpg(960x640, 20.4 kB)

Silhouette Of People - silhouette-of-people.jpg(1280x851, 127 kB), silhouette-of-people-temp.jpg(1280x851, 26.5 kB)

If we calculate the total size of both temp versions and actual version, a difference of up to 310.1 kB can be observed. We can save this additional bandwidth of 310.1 kB on the page loaded initially without these images. We might need to consider the images to be loaded at the start in case these are part of the initial view covered by the Viewport. In case the user scrolls down, the placeholder images will be replaced by the actual images.

Scroll Listener

Using listeners to scroll and resize is the option to be used on almost all the browsers having JavaScript enabled. This approach uses the JavaScript method getBoundingClientRect to determine whether the image is intersecting with the viewport area and also check whether the image is actually visible.

// On document load

// Best to place either at the start of body tag or at the end of head tag

document.addEventListener( "DOMContentLoaded", function() {



// Images collection

var images = [].slice.call( document.querySelectorAll( 'img.lazy-img' ) );



// Flag to relax the listener from continuous checking

var active = false;



// The actual listener to listen for scroll, resize and change in viewport orientation

var lazyLoadListener = function() {



if( active === false ) {



active = true;



setTimeout( function() {



// Iterate over the images collection for lazy loading

images.forEach( function( image ) {



// Window intersection test

if( ( image.getBoundingClientRect().top <= window.innerHeight && image.getBoundingClientRect().bottom >= 0 ) && getComputedStyle( image ).display !== "none" ) {



image.src = image.dataset.src;

image.srcset = image.dataset.srcset;



// Done lazy loading

image.classList.remove( '.lazy-img' );



// Remove from collection

images = images.filter( function( img ) {



return image !== img;

});



// Stop lazy loading

if( images.length === 0 ) {



document.removeEventListener( 'scroll', lazyLoadListener );

window.removeEventListener( 'resize', lazyLoadListener );

window.removeEventListener( 'orientationchange', lazyLoadListener );

}

}

});



active = false;

}, 200 );

}

};



document.addEventListener( 'scroll', lazyLoadListener );

window.addEventListener( 'resize', lazyLoadListener );

window.addEventListener( 'orientationchange', lazyLoadListener );

});

Intersection Observer

Intersection observer is easier to implement, but not supported on all the browsers. The newer version of modern browsers support intersection observer and can simply implement it by registering an observer to watch the target element and unobserve as soon as the actual image loads. We must also provide fallback to intersection observer to support a wider range of browsers.

// jQuery implementation, assuming jQuery is already loaded

// On document load

jQuery( document ).ready( function() {



// Images collection

var images = jQuery( 'img.lazy-img' );



// Insersection Observer support

if( "IntersectionObserver" in window ) {



// Window observer

intersectionObserver = new IntersectionObserver( function( entries, observer ) {



// Iterate observer collection

entries.forEach( function( entry ) {



// Window intersection test

if( entry.isIntersecting ) {



image = entry.target;

image.src = image.dataset.src;

image.srcset = image.dataset.srcset;



// Done lazy loading

image.classList.remove( '.lazy-img' );



// Remove from observer

observer.unobserve( image );

}

});

});



// Observer collection

images.each( function() {



intersectionObserver.observe( this );

});

}

else {



// Use fallback using scroll listener

}

});

// JavaScript implementation without jQuery

// On document load

// Preferred over jQuery implementation, since it has to be the first one to be loaded without any network overhead

// Best to place either at the start of body tag or at the end of head tag

document.addEventListener( "DOMContentLoaded", function() {



// Images collection

var images = [].slice.call( document.querySelectorAll( 'img.lazy-img' ) );



// Insersection Observer support

if( "IntersectionObserver" in window ) {



// Window observer

intersectionObserver = new IntersectionObserver( function( entries, observer ) {



// Iterate observer collection

entries.forEach( function( entry ) {



// Window intersection test

if( entry.isIntersecting ) {



image = entry.target;

image.src = image.dataset.src;

image.srcset = image.dataset.srcset;



// Done lazy loading

image.classList.remove( '.lazy-img' );



// Remove from observer

observer.unobserve( image );

}

});

});



// Observer collection

images.forEach( function( lazyImage ) {



intersectionObserver.observe( lazyImage );

});

}

else {



// Use fallback using scroll listener

}

});





Existing Solution

We can also use the existing solutions instead of implementing own solution. This way, we can save time and instantly implement the lazy loading of images. The popular solutions are as listed below

Lozad.js - Description from GitHub - Highly performant, light ~1kb and configurable lazy loader in pure JS with no dependencies for responsive images, iframes and more.

lazysizes - Description from GitHub - High performance and SEO friendly lazy loader for images (responsive and normal), iframes and more, that detects any visibility changes triggered through user interaction, CSS or JavaScript without configuration. It's similar to the solutions discussed in this tutorial.

Yall.js - Description from GitHub - A fast, flexible, and small SEO-friendly lazy loader.

The complete implementation to lazy load the images with fallback solution as explained in the first two sections of this tutorial is as mentioned below.

See the Pen Lazy loading images by Bhagwat Chouhan (@bhagwatchouhan) on CodePen.