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 definitely plays a role in improving page speed by loading images asynchronously.
Lazy loading also known as Defer Off-Screen Images Lazy loading images is an approach used to load an image only when it is needed in viewport. All images which are off the screen won't load. This approach reduces the load time of a page and helps pages to load faster. Lets start writing code
index.php
HTML content page holding the code with loop to show images with class "lazy-image" added to it. We are not assigning any "src" attribute to image but rather we use "data-src" attribute which will hold the real path of the image. This "data-src" will be used to load image's src using javascript when the image is visible to viewport.
<!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>
javascript.js
To ensure maximum compatibility are using event handlers to lazy load images. Select all the images with class "lazy-image" and loop through all the images, next we check if any of image is visible in viewport we load the src of image from its dataset src.
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);
});
One other approach to achieve image lazy load is to use javascript's intersection observer when can be used considering the browser compatibility at the time of implementation.
document.addEventListener("DOMContentLoaded", function() {
var lazyImages = [].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 lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src;
lazyImage.classList.add("loaded");
lazyImageObserver.unobserve(lazyImage);
}
});
},{rootMargin: "0px 0px -120px 0px"});
lazyImages.forEach(function(lazyImage) {
lazyImageObserver.observe(lazyImage);
});
}
});
Now if you feel it is a lot to do to manually add image-lazy class to all existing images for lazy load. This block of useful code will make the job easier, 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);
style.css
Styles for our html page and lazy images.*{
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;
}