Lexington has been awarded a grant from Astro, to celebrate. Get a 30% discount. Apply code LEXINGTON30 ( uppercase ) at checkout.
Finally Friday! We’re back with another tutorial on how to build a lazy-loading image gallery with Tailwind CSS and JavaScript.
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.
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.
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>
We’ll use the createImageElement
function to create the image elements dynamically.
const div = document.createElement("div");
creates a new div
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",
];
createImageElement
functionThis 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 new div
element.div.className = "relative overflow-hidden aspect-w-16 aspect-h-9";
: Sets the class name of the div
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 new img
element.img.className = "lazy-image w-full h-full object-cover transition-opacity duration-300 opacity-0";
: Sets the class name of the img
element to “lazy-image w-full h-full object-cover transition-opacity duration-300 opacity-0”.img.dataset.src = url;
: Sets the src
attribute of the img
element to the URL of the image.img.alt =
Image ${index + 1};
: Sets the alt
attribute of the img
element to “Image 1”, “Image 2”, etc.const placeholder = document.createElement("div");
: Creates a new div
element.placeholder.className = "absolute inset-0 bg-neutral-200 animate-pulse w-full h-full";
: Sets the class name of the placeholder
element to “absolute inset-0 bg-neutral-200 animate-pulse w-full h-full”.div.appendChild(placeholder);
: Appends the placeholder
element to the div
element.div.appendChild(img);
: Appends the img
element to the div
element.return div;
: Returns the div
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 =
"lazy-image w-full h-full object-cover transition-opacity duration-300 opacity-0";
img.dataset.src = url;
img.alt = `Image ${index + 1}`;
const placeholder = document.createElement("div");
placeholder.className = "absolute inset-0 bg-neutral-200 animate-pulse w-full h-full";
div.appendChild(placeholder);
div.appendChild(img);
return div;
}
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 the img
elements with the class lazy-image
.const images = document.querySelectorAll("img.lazy-image");
: Selects all img
elements with the class lazy-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 the src
attribute of the img
element to the value of the dataset.src
attribute.img.onload = () => {
: Triggers the onload
event of the img
element.img.classList.remove("opacity-0");
: Removes the class opacity-0
from the img
element.img.previousElementSibling.remove(); // Remove placeholder
: Removes the placeholder element from the img
element.observer.unobserve(img);
: Unobserves the img
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));
}
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 the imageUrls
array.const imageElement = createImageElement(url, index);
: Calls the createImageElement
function with the URL and index as arguments.gallery.appendChild(imageElement);
: Appends the imageElement
to the gallery
element.lazyLoad();
: Calls the lazyLoad
function.// Create and append image elements
imageUrls.forEach((url, index) => {
const imageElement = createImageElement(url, index);
gallery.appendChild(imageElement);
});
// Initialize lazy loading
lazyLoad();
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 =
"lazy-image w-full h-full object-cover transition-opacity duration-300 opacity-0";
img.dataset.src = url;
img.alt = `Image ${index + 1}`;
const placeholder = document.createElement("div");
placeholder.className = "absolute inset-0 bg-neutral-200 animate-pulse w-full h-full";
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();
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
Get lifetime access to every theme available today for $199 and own
them forever. Plus, new themes, lifetime updates, use on unlimited
projects and enjoy lifetime support.
— No subscription required!