
Finally Friday! We’re back with another tutorial on how to build a lazy-loading image gallery with Tailwind CSS and JavaScript.
What is lazy-loading?
Lazy-loading is a performance optimization technique where images or other media are only loaded when they enter the viewport (or are about to). Instead of loading all images on page load, lazy-loading helps reduce initial load times, as only the necessary content is loaded first. This results in faster page performance and an overall smoother user experience, especially on image-heavy websites.
Use cases for lazy-loading images
Lazy-loading is particularly useful in the following scenarios:
-
Image galleries: When displaying a large number of images, lazy-loading ensures that only the images in view are loaded, preventing long load times and conserving bandwidth.
-
Long pages: On content-heavy pages (e.g., blogs or e-commerce product listings), lazy-loading helps keep the page load time down by loading images as the user scrolls.
-
Mobile optimization: For users on slower networks or mobile devices, lazy-loading ensures they only download necessary content, preserving data and improving the browsing experience.
-
Progressive web apps (PWAs): Lazy-loading helps maintain the performance of PWAs, ensuring they remain fast and responsive.
In this tutorial, we’ll walk through how to set up a lazy-loading image gallery using Tailwind CSS for styling and a bit of JavaScript to implement the lazy-loading functionality.
Writting the markup
Id’s
gallery
is the id of the container element that will hold the images. This is where we’ll append the dynamically generated image elements. Classesgrid
is a utility class that allows us to create a grid layout for the images.grid-cols-1
sets the number of columns to 1, which will create a single column layout.sm:grid-cols-2
sets the number of columns to 2 for screens with a small width (up to 768px).md:grid-cols-3
sets the number of columns to 3 for screens with a medium width (768px to 1024px).lg:grid-cols-4
sets the number of columns to 4 for screens with a large width (1024px and up).gap-4
adds a 4-pixel gap between the images.
<div
id="gallery"
class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
<!-- Images will be dynamically inserted here -->
</div>
Writing the JavaScript
Creating the image elements
We’ll use the createImageElement
function to create the image elements dynamically.
const div = document.createElement("div");
creates a newdiv
element. We’ll use this element to wrap the image and the placeholder.const imagesUrls = [.... ];
is an array of image URLs. We’ll use this array to create the image elements.
const gallery = document.getElementById("gallery");
const imageUrls = [
"https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?q=80&w=1856&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1516914943479-89db7d9ae7f2?q=80&w=2732&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1531384698654-7f6e477ca221?q=80&w=2800&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1531901599143-df5010ab9438?q=80&w=2787&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1524255684952-d7185b509571?q=80&w=2787&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1588175996685-a40693ee1087?q=80&w=2864&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1624561172888-ac93c696e10c?q=80&w=2592&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1489424731084-a5d8b219a5bb?q=80&w=2787&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
];
The createImageElement
function
This function takes two arguments: the URL of the image and the index of the image in the array. It returns a new div
element that contains the image and a placeholder
element to display while the image is loading.
function createImageElement(url, index) {
: This function takes two arguments: the URL of the image and the index of the image in the array.const div = document.createElement("div");
: Creates a newdiv
element.div.className = "relative overflow-hidden aspect-w-16 aspect-h-9";
: Sets the class name of thediv
element to “relative overflow-hidden aspect-w-16 aspect-h-9”. This class sets the element to be a relative container with overflow hidden and an aspect ratio of 16:9.const img = document.createElement("img");
: Creates a newimg
element.img.className = "object-cover w-full h-full opacity-0 lazy-image transition-opacity duration-300";
: Sets the class name of theimg
element to “lazy-image w-full h-full object-cover transition-opacity duration-300 opacity-0”.img.dataset.src = url;
: Sets thesrc
attribute of theimg
element to the URL of the image.img.alt =
Image ${index + 1};
: Sets thealt
attribute of theimg
element to “Image 1”, “Image 2”, etc.const placeholder = document.createElement("div");
: Creates a newdiv
element.placeholder.className = "absolute inset-0 w-full h-full bg-base-200 animate-pulse";
: Sets the class name of theplaceholder
element to “absolute inset-0 bg-base-200 animate-pulse w-full h-full”.div.appendChild(placeholder);
: Appends theplaceholder
element to thediv
element.div.appendChild(img);
: Appends theimg
element to thediv
element.return div;
: Returns thediv
element.
function createImageElement(url, index) {
const div = document.createElement("div");
div.className = "relative overflow-hidden aspect-w-16 aspect-h-9";
const img = document.createElement("img");
img.className =
"object-cover w-full h-full opacity-0 lazy-image transition-opacity duration-300";
img.dataset.src = url;
img.alt = `Image ${index + 1}`;
const placeholder = document.createElement("div");
placeholder.className = "absolute inset-0 w-full h-full bg-base-200 animate-pulse";
div.appendChild(placeholder);
div.appendChild(img);
return div;
}
The lazyLoad function
This function uses the IntersectionObserver API to observe the img
elements with the class lazy-image
. When an element with the class lazy-image
is intersected with the viewport, the src
attribute of the element is set to the value of the dataset.src
attribute, and the onload
event is triggered.
function lazyLoad() {
: This function uses the IntersectionObserver API to observe theimg
elements with the classlazy-image
.const images = document.querySelectorAll("img.lazy-image");
: Selects allimg
elements with the classlazy-image
.const options = {
: Creates an object with the options for the IntersectionObserver.root: null,
: Sets the root option to null.rootMargin: "0px",
: Sets the rootMargin option to “0px”.threshold: 0.1,
: Sets the threshold option to 0.1.const imageObserver = new IntersectionObserver((entries, observer) => {
: Creates a new IntersectionObserver with the provided callback function.entries.forEach((entry) => {
: Iterates over each entry in the IntersectionObserver.if (entry.isIntersecting) {
: Checks if the entry is intersecting.const img = entry.target;
: Selects the target element of the entry.img.src = img.dataset.src;
: Sets thesrc
attribute of theimg
element to the value of thedataset.src
attribute.img.onload = () => {
: Triggers theonload
event of theimg
element.img.classList.remove("opacity-0");
: Removes the classopacity-0
from theimg
element.img.previousElementSibling.remove(); // Remove placeholder
: Removes the placeholder element from theimg
element.observer.unobserve(img);
: Unobserves theimg
element.
function lazyLoad() {
const images = document.querySelectorAll("img.lazy-image");
const options = {
root: null,
rootMargin: "0px",
threshold: 0.1,
};
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.onload = () => {
img.classList.remove("opacity-0");
img.previousElementSibling.remove(); // Remove placeholder
};
observer.unobserve(img);
}
});
}, options);
images.forEach((img) => imageObserver.observe(img));
}
Creating and appending the image elements
We’ll create and append the image elements to the gallery using the createImageElement
function. We’ll also initialize the lazy loading by calling the lazyLoad
function.
imageUrls.forEach((url, index) => {
: Iterates over each URL in theimageUrls
array.const imageElement = createImageElement(url, index);
: Calls thecreateImageElement
function with the URL and index as arguments.gallery.appendChild(imageElement);
: Appends theimageElement
to thegallery
element.lazyLoad();
: Calls thelazyLoad
function.
// Create and append image elements
imageUrls.forEach((url, index) => {
const imageElement = createImageElement(url, index);
gallery.appendChild(imageElement);
});
// Initialize lazy loading
lazyLoad();
The full script
const gallery = document.getElementById("gallery");
const imageUrls = [
"https://images.unsplash.com/photo-1543610892-0b1f7e6d8ac1?q=80&w=1856&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1516914943479-89db7d9ae7f2?q=80&w=2732&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1531384698654-7f6e477ca221?q=80&w=2800&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1531901599143-df5010ab9438?q=80&w=2787&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1524255684952-d7185b509571?q=80&w=2787&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1588175996685-a40693ee1087?q=80&w=2864&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1624561172888-ac93c696e10c?q=80&w=2592&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
"https://images.unsplash.com/photo-1489424731084-a5d8b219a5bb?q=80&w=2787&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D",
];
function createImageElement(url, index) {
const div = document.createElement("div");
div.className = "relative overflow-hidden aspect-w-16 aspect-h-9";
const img = document.createElement("img");
img.className =
"object-cover w-full h-full opacity-0 lazy-image transition-opacity duration-300";
img.dataset.src = url;
img.alt = `Image ${index + 1}`;
const placeholder = document.createElement("div");
placeholder.className = "absolute inset-0 w-full h-full bg-base-200 animate-pulse";
div.appendChild(placeholder);
div.appendChild(img);
return div;
}
function lazyLoad() {
const images = document.querySelectorAll("img.lazy-image");
const options = {
root: null,
rootMargin: "0px",
threshold: 0.1,
};
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.onload = () => {
img.classList.remove("opacity-0");
img.previousElementSibling.remove(); // Remove placeholder
};
observer.unobserve(img);
}
});
}, options);
images.forEach((img) => imageObserver.observe(img));
}
// Create and append image elements
imageUrls.forEach((url, index) => {
const imageElement = createImageElement(url, index);
gallery.appendChild(imageElement);
});
// Initialize lazy loading
lazyLoad();
Conclusion
In this tutorial, we learned how to create a lazy-loading image gallery using Tailwind CSS and JavaScript. We covered topics such as creating a lazy-loading image gallery, handling image loading, and implementing lazy-loading functionality.
Hope you enjoyed this tutorial and have a great day!
/Michael Andreuzza
Unlock all themes for $199$139 and own them forever! Includes lifetime
updates, new themes, unlimited projects, and support
