Lazy Load Images with Javascript

A website's page speed is an important factor that should be taken into consideration when optimizing a website. Lazy loading images plays a vital role in improving page speed with image lazy load technique.

Lazy Load Images with Javascript

Lazy loading images sometimes known as image defer loading is an approach used to load an image only when it is in viewport. With lazy load image approach we do not load off screen images.. This approach reduces the load time of a page and helps pages to load faster. In this post we are going to learn how to lazy load images in JavaScript. We are going to create following files for this post:

  • index.php: Our main HTML page containing the images for lazy loading.
  • javascript.js: The JavaScript code to implement lazy loading of images.
  • style.css: CSS styles for whole HTML page.
 

Render Images for Lazy Load

Create an HTML page and render images in a loop. 

  • Add class "lazy-image" to each image on page that is supposed to lazy load. 
  • Add "data-src" which will hold the source location of image and we will assign src attribute to image using this data-src attribute.

index.php

<!DOCTYPE html>
<html>
<head>
<title>Lazy Load Images with Javascript - Demo</title>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<div class="container">
<div class="my-4">
<?php for ($i = 1; $i <= 12; $i++) { ?>
<span class="has-lazy-image">
<img class="image-responsive lazy-image" data-src="images/lazy-image-<?=$i;?>.jpg"
alt="Lazy Load Image <?=$i;?>"
title="Lazy Load Image <?=$i;?>"
width="640" height="420" />
</span>
<?php } ?>
</div>
</div>
</body>
<footer>
<script type="text/javascript" src="js/javascript.js"></script>
</footer>
</html>

Lazy Load Images in JavaScript

There are two ways we can lazy load images in javascript. To avoid compatibility issues custom code to detected if image is in viewport and should be loaded. Steps to lazy load images in javascript: 

  • Add a DOMContentLoaded event listener to document.
  • Select all the images with class "lazy-image" but not having the class "loaded". 
  • Create a function lazyLoad to lazy load images in lazy images array and add event listeners "DOMContentLoaded", "orientationchange", "scroll" and "resize".
  • Inside function loop through all images and check if current image in viewport using getBoundingClientRect() and comparing it with window's inner height. 
  • Also check if image is currently not hidden we do not want to show those images.
  • Assign height attribute to image using value from data-height attribute.
  • Assign src attribute using value from data-src attribute.
  • Add class "loaded" to image.
  • Remove the image from lazy images array.
  • Remove all event listeners for lazyLoad function if there are no images left in lazy images array.

javascript.js

document.addEventListener("DOMContentLoaded", function () {
let active = false;
let lazy_images = [].slice.call(document.querySelectorAll(".lazy-image:not(.loaded)"));

const lazyLoad = function () {
if (active === false) {
active = true;

setTimeout(function () {
lazy_images.forEach(function (lazy_image) {
if ((lazy_image.getBoundingClientRect().top <= window.innerHeight && lazy_image.getBoundingClientRect().bottom >= 0)
&& getComputedStyle(lazy_image).display !== "none") {
lazy_image.style.height = lazy_image.dataset.height || null;

lazy_image.src = lazy_image.dataset.src;
lazy_image.classList.add("loaded");

lazy_image.removeAttribute("data-src");
lazy_image.removeAttribute("data-height");

lazy_images = lazy_images.filter(function (image) {
return image !== lazy_image;
});

if (lazy_images.length === 0) {
window.removeEventListener("scroll", lazyLoad);
window.removeEventListener("resize", lazyLoad);
window.removeEventListener("orientationchange", lazyLoad);
}
}
});

active = false;
}, 200);
}
};

document.addEventListener("DOMContentLoaded", lazyLoad);
window.addEventListener("orientationchange", lazyLoad);
window.addEventListener("scroll", lazyLoad);
window.addEventListener("resize", lazyLoad);
});

Lazy Load Images with Intersection Observer

One other approach to achieve image lazy load is to use javascript's intersection observer which can be used considering the browser compatibility at the time of implementation. Steps to lazy load images with intersection observer:

  • Add a DOMContentLoaded event listener to document.
  • Select all the images with class "lazy-image" but not having the class "loaded" and loop through all the images.
  • Check if browser supports intersection observer.
  • Create intersection observer for images. 
  • Inside intersection observer check if for each image entry if it is in viewport.
  • Assign src attribute using value from data-src attribute.
  • Add class "loaded" to image.
  • Finally outside the function we created, loop through all images and assign the intersection observer to each image.
document.addEventListener("DOMContentLoaded", function() {
var lazy_images = [].slice.call(document.querySelectorAll(".lazy-image:not(.loaded)"));

if ("IntersectionObserver" in window) {
let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
let lazy_image = entry.target;
lazy_image.src = lazyImage.dataset.src;
lazy_image.classList.add("loaded");
lazyImageObserver.unobserve(lazy_image);
}
});
},{rootMargin: "0px 0px -120px 0px"});

lazy_images.forEach(function(lazy_image) {
lazyImageObserver.observe(lazy_image);
});
}
});

Add Lazy Loading Class to All Images

If you feel it is a lot to do, to manually add lazy-image class to all existing images for lazy load. This block of useful code will make the job easier for you. Place this code right before lazy load code, This code will add "lazy-image" class to all of images.

(function(document){
let images = [].slice.call(document.querySelectorAll("img"));
images.forEach(function (image) {
image.dataset.height = image.style.height;
image.dataset.src = image.src;

image.src = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzIDIiPjwvc3ZnPg==";
image.classList.add("lazy-image");

image.style.height = image.height + 'px';
});
})(document);

Add CSS Styles

Styles for our entire HTML page and lazy images. 

style.css

*{
box-sizing: border-box;
}
html,body{
margin: 0;
padding: 0;
}
body{
background-color: #f6f6f6;
font-family: "Segoe UI", "Roboto", "Helvetica", sans-serif;
font-size: 15px;
font-weight: normal;
font-style: normal;
line-height: 1.5;
}
.container{
width: 100%;
max-width: 1140px;
margin-right: auto;
margin-left: auto;
padding-right: 15px;
padding-left: 15px;
}
.my-4{
margin-top: 1rem;
margin-bottom: 1rem;
}
.has-lazy-image{
display: inline-block;
border: 5px solid #fff;
margin-bottom: 1rem;
box-shadow: 0 0 5px rgba(0,0,0,0.5);
}
.column-left .lazy-image{
opacity: 0;
transition: opacity ease-in .2s;
}
.image-responsive{
display: block;
max-width: 100%;
height: auto;
}
.lazy-image.loaded{
opacity: 1;
}