Hey guys welcome. In this tutorial, you will learn How to Make a Lazy Loading Image Gallery with HTML, CSS3, JavaScript & Bootstrap 5 (+ Angular, React & Vue Examples).

Modern websites often showcase high-resolution images to enhance user experience and visual appeal. However, loading all those images at once can significantly slow down the site, especially on mobile devices or slow networks. This is where lazy loading becomes an essential performance technique.
What is Lazy Loading?
Lazy loading is a web optimization technique where images (or other resources) are only loaded when they are about to enter the user’s viewport — that is, when they’re visible on the screen. Instead of fetching all images upfront, the browser loads them on-demand, conserving bandwidth and speeding up initial page load time.
Why Use Lazy Loading in Image Galleries?
Image galleries often contain dozens or even hundreds of media files. Without lazy loading, all images load at once, regardless of whether the user scrolls down to see them or not. This results in:
- Slower page load times
- Higher bounce rates
- Increased server load
- Poor user experience on low-bandwidth connections
Lazy loading prevents these issues by loading images only when needed, making the gallery faster and more responsive.
SEO, Performance, and UX Benefits
Lazy loading isn’t just about performance — it also has a direct impact on SEO and user satisfaction.
- SEO: Search engines like Google support lazy-loaded content when implemented correctly. Faster pages often rank higher in search results.
- Performance: Reduces the number of HTTP requests on initial load, leading to quicker rendering and lower resource consumption.
- User Experience: Provides a smoother experience, especially on long-scrolling pages. Users see the content they care about immediately without waiting for hidden assets to load.
Tools and Technologies We’ll Use
In this tutorial, we’ll explore how to build a lazy loading image gallery using different frontend technologies:
- HTML5 and CSS3: To build the basic structure and style the layout
- JavaScript: To implement the lazy loading logic using
IntersectionObserver - Bootstrap 5: To speed up responsive layout design with utility classes and grid system
- Angular: To demonstrate lazy loading in a component-driven framework
- React: To build the same gallery using hooks and functional components
- Vue: To create the gallery using directives and component-based architecture
Whether you’re working with plain HTML or a full framework like Angular or React, you’ll learn how to implement lazy loading effectively for any kind of project.
Core Concepts of Lazy Loading
What Happens When You Don’t Lazy Load Images?
When a web page contains many images and none of them are lazy loaded, the browser attempts to download all of them immediately—even the ones that are far down the page and may never be seen by the user. This approach leads to:
- Longer page load times, especially on slower networks
- High memory usage, which can crash or freeze low-end devices
- Lower Core Web Vitals scores, which can negatively affect search rankings
- Wasted bandwidth, especially if users leave before scrolling through the full gallery
This is especially problematic for image-heavy galleries or long-scrolling pages.
Native Lazy Loading with loading="lazy"
Modern browsers now support native lazy loading for images and iframes using the loading attribute. To lazy load an image, simply add the loading="lazy" attribute to the <img> tag:
<img src="gallery/photo1.jpg" alt="Sample Photo" loading="lazy">Code language: HTML, XML (xml)
This instructs the browser to defer loading the image until it is close to appearing in the viewport. Native lazy loading is:
- Simple to implement (no JavaScript required)
- Widely supported in most modern browsers
- Great for basic use cases
However, if you need more control (e.g., fade-in effects, custom behavior, or support for background images), JavaScript-based lazy loading may be a better choice.
Lazy Loading vs. Progressive Loading vs. Infinite Scrolling
These three terms are often confused, but they solve different problems:
| Feature | Lazy Loading | Progressive Loading | Infinite Scrolling |
|---|---|---|---|
| Purpose | Delay loading offscreen images | Load a low-res version first | Load more content as user scrolls |
| When to Use | Image galleries, blogs, product listings | Photo-heavy pages where loading feedback matters | Social feeds, news apps, long lists |
| Tech Used | loading="lazy", IntersectionObserver | Blurred image previews, LQIP | JS listeners for scroll or IntersectionObserver |
| User Control | Full control with scroll | Immediate preview, clearer on load | Content appears automatically, no pagination |
Understanding these differences helps you choose the right technique for your use case. In this post, our focus is lazy loading, which is ideal for optimizing image-heavy pages without requiring extra backend logic or content fetching.
Build the Base Gallery with HTML5 + CSS3
Before we integrate lazy loading or JavaScript logic, let’s first create a simple and responsive image gallery using just HTML5 and CSS3. This foundation will allow us to easily add lazy loading and framework-specific implementations in later sections.
1. Setup HTML Document and Gallery Structure
We’ll begin with a clean HTML5 boilerplate and add a basic image gallery using semantic markup.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lazy Load Image Gallery</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="gallery">
<img src="placeholder.jpg" data-src="images/photo1.jpg" alt="Photo 1" class="lazy-image">
<img src="placeholder.jpg" data-src="images/photo2.jpg" alt="Photo 2" class="lazy-image">
<img src="placeholder.jpg" data-src="images/photo3.jpg" alt="Photo 3" class="lazy-image">
<!-- Add more images as needed -->
</main>
</body>
</html>
Code language: HTML, XML (xml)
Explanation:
placeholder.jpgis a low-resolution image or blank image shown initially.data-srcstores the actual image path that will be loaded via JavaScript later.- The
lazy-imageclass will help target these images with CSS and JS.
2. Style the Layout Using CSS Grid
Next, let’s create a clean, responsive layout using CSS Grid.
/* style.css */
body {
margin: 0;
font-family: sans-serif;
background-color: #f4f4f4;
}
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
padding: 2rem;
max-width: 1200px;
margin: auto;
}
.gallery img {
width: 100%;
height: auto;
border-radius: 8px;
object-fit: cover;
transition: opacity 0.3s ease-in-out;
}
Code language: CSS (css)
Explanation:
auto-fit+minmaxensures the gallery adapts to various screen sizes.gapdefines spacing between images.border-radiusandtransitiongive a polished feel.
3. Add Responsive Behavior with Media Queries
Even though CSS Grid is already responsive, we can tweak padding and spacing on smaller screens using media queries.
@media (max-width: 600px) {
.gallery {
padding: 1rem;
gap: 0.5rem;
}
}
Code language: CSS (css)
This ensures the gallery maintains a clean layout and spacing on mobile devices as well.
At this point, we have a static, responsive image gallery ready. All images are currently showing placeholders. In the next section, we’ll bring this gallery to life by using JavaScript to dynamically lazy load the actual images when they come into view.
Make It Interactive with JavaScript
Now that we have a static gallery built with HTML5 and CSS3, it’s time to make it functional and efficient using JavaScript. We’ll use the IntersectionObserver API to detect when an image is about to enter the viewport and then replace the placeholder with the actual image from data-src.
1. Detecting Image Visibility Using IntersectionObserver
The IntersectionObserver API lets us monitor elements as they scroll into view, making it ideal for lazy loading.
Here’s how we can use it:
// script.js
document.addEventListener("DOMContentLoaded", () => {
const lazyImages = document.querySelectorAll("img.lazy-image");
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.onload = () => img.classList.add("loaded"); // For fade-in effect
observer.unobserve(img);
}
});
});
lazyImages.forEach(img => observer.observe(img));
});
Code language: JavaScript (javascript)
Explanation:
- We wait for the DOM to load before executing.
- We target all
.lazy-imageelements. - When an image is in view (
entry.isIntersecting), we:- Replace
srcwithdata-src - Add a
.loadedclass after it’s fully loaded - Unobserve the image to stop checking it
- Replace
2. Fallback for Older Browsers (Optional)
If you want to support older browsers that don’t support IntersectionObserver, you can add a simple fallback:
if (!("IntersectionObserver" in window)) {
// Fallback: Load all images at once
const lazyImages = document.querySelectorAll("img.lazy-image");
lazyImages.forEach(img => {
img.src = img.dataset.src;
img.onload = () => img.classList.add("loaded");
});
}
Code language: JavaScript (javascript)
While this removes the “lazy” part, it ensures all images load without breaking the layout on unsupported browsers.
3. Add Lazy Loading Logic to Replace Placeholder
Let’s also add the script.js file to our HTML to activate the functionality:
<!-- At the end of body -->
<script src="script.js"></script>
Code language: HTML, XML (xml)
This completes the lazy loading mechanism for our HTML + CSS gallery.
4. Bonus: Add Fade-In Effect on Image Load
We already added a .loaded class in the JavaScript when the actual image is loaded. Now let’s create the CSS for it:
/* Fade-in effect */
.lazy-image {
opacity: 0;
transition: opacity 0.5s ease-in-out;
}
.lazy-image.loaded {
opacity: 1;
}
Code language: CSS (css)
This gives a smooth fade-in transition when the real image replaces the placeholder, enhancing visual polish.
At this point, you now have a fully functional, performance-optimized image gallery that lazy loads images as users scroll.
Enhance with Bootstrap 5
Bootstrap 5 makes it easier to create responsive layouts, manage spacing, and apply consistent styles across components. In this section, we’ll integrate Bootstrap into our image gallery to enhance layout, design, and responsiveness — without sacrificing our lazy loading logic.
1. Use Bootstrap’s Grid System for Layout
We’ll use Bootstrap’s grid system to replace our custom CSS grid with responsive rows and columns.
Here’s the updated HTML structure:
<!-- Inside <body> -->
<div class="container py-5">
<div class="row g-4">
<div class="col-12 col-sm-6 col-md-4 col-lg-3" >
<img src="placeholder.jpg" data-src="images/photo1.jpg" alt="Photo 1" class="img-fluid lazy-image rounded">
</div>
<div class="col-12 col-sm-6 col-md-4 col-lg-3" >
<img src="placeholder.jpg" data-src="images/photo2.jpg" alt="Photo 2" class="img-fluid lazy-image rounded">
</div>
<!-- Repeat for more images -->
</div>
</div>
Code language: HTML, XML (xml)
Explanation:
containerensures consistent horizontal padding.row g-4creates a flex grid with responsive spacing (g-4 = gap: 1.5rem).col-12 col-sm-6 col-md-4 col-lg-3ensures adaptive column widths across breakpoints.
2. Use Bootstrap’s Responsive Image and Card Components
You can optionally wrap each image in a Bootstrap card for more control and visual framing:
<div class="col-12 col-sm-6 col-md-4 col-lg-3">
<div class="card">
<img src="placeholder.jpg" data-src="images/photo3.jpg" alt="Photo 3" class="card-img-top lazy-image">
</div>
</div>
Code language: JavaScript (javascript)
Or, keep it clean with just img-fluid and rounded if cards feel too bulky.
3. Style Enhancements with Bootstrap Utility Classes
Use utility classes to quickly improve spacing, borders, and alignment:
rounded– Adds subtle rounded corners to imagesshadow-smorshadow– Adds soft drop shadowsmb-3orp-2– Controls spacingtext-center– For captions or titles
Example:
<img src="placeholder.jpg" data-src="images/photo4.jpg"
alt="Photo 4"
class="img-fluid lazy-image rounded shadow-sm">
Code language: JavaScript (javascript)
4. Integrate the Lazy Loading Logic into Bootstrap Markup
The lazy loading logic in script.js remains exactly the same — because we’re still targeting all .lazy-image elements.
Just ensure the updated structure includes:
src="placeholder.jpg"for the placeholderdata-src="..."for the actual imageclass="lazy-image"for the script to detectimg-fluidfor responsive resizing
Example inside Bootstrap layout:
<img src="placeholder.jpg"
data-src="images/photo5.jpg"
alt="Photo 5"
class="img-fluid lazy-image rounded shadow">
Code language: JavaScript (javascript)
The result: a visually appealing, responsive, and performance-optimized image gallery that works seamlessly with Bootstrap’s design system.
Real-World Example – HTML + JS + Bootstrap 5 Gallery (Code Demo)
Let’s put all the pieces together and build a working lazy loading image gallery using:
- HTML for structure
- CSS for styling and fade-in effect
- Bootstrap 5 for layout and responsiveness
- JavaScript for lazy loading logic
1. Final HTML Structure
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lazy Load Gallery</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container py-5">
<h2 class="text-center mb-4">Lazy Loading Image Gallery</h2>
<div class="row g-4">
<div class="col-12 col-sm-6 col-md-4 col-lg-3">
<img src="placeholder.jpg" data-src="images/photo1.jpg" alt="Photo 1" class="img-fluid lazy-image rounded shadow-sm">
</div>
<div class="col-12 col-sm-6 col-md-4 col-lg-3">
<img src="placeholder.jpg" data-src="images/photo2.jpg" alt="Photo 2" class="img-fluid lazy-image rounded shadow-sm">
</div>
<div class="col-12 col-sm-6 col-md-4 col-lg-3">
<img src="placeholder.jpg" data-src="images/photo3.jpg" alt="Photo 3" class="img-fluid lazy-image rounded shadow-sm">
</div>
<div class="col-12 col-sm-6 col-md-4 col-lg-3">
<img src="placeholder.jpg" data-src="images/photo4.jpg" alt="Photo 4" class="img-fluid lazy-image rounded shadow-sm">
</div>
<!-- Add more images as needed -->
</div>
</div>
<script src="script.js"></script>
</body>
</html>
Code language: HTML, XML (xml)
2. CSS Snippet (style.css)
body {
background-color: #f8f9fa;
font-family: sans-serif;
}
/* Fade-in transition */
.lazy-image {
opacity: 0;
transition: opacity 0.5s ease-in-out;
}
.lazy-image.loaded {
opacity: 1;
}
Code language: CSS (css)
3. JavaScript Lazy Load Logic (script.js)
document.addEventListener("DOMContentLoaded", () => {
const lazyImages = document.querySelectorAll("img.lazy-image");
// Fallback for browsers without IntersectionObserver
if (!("IntersectionObserver" in window)) {
lazyImages.forEach(img => {
img.src = img.dataset.src;
img.onload = () => img.classList.add("loaded");
});
return;
}
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.onload = () => img.classList.add("loaded");
observer.unobserve(img);
}
});
});
lazyImages.forEach(img => observer.observe(img));
});
Code language: JavaScript (javascript)
4. Sample Images (with Placeholders)
You can use the following sample image URLs for quick testing:
- Placeholder:
https://via.placeholder.com/300x200?text=Loading... - Actual images:
Replaceimages/photo1.jpgwith:https://picsum.photos/id/1018/600/400 https://picsum.photos/id/1025/600/400 https://picsum.photos/id/1035/600/400 https://picsum.photos/id/1043/600/400
Or use your own images in a /images/ folder.
This demo gives you a production-ready, responsive image gallery that loads images only when needed — reducing bandwidth and improving page speed.
Implement Lazy Load Gallery in Angular
In this section, we’ll rebuild the lazy loading gallery using Angular’s component-based structure. You’ll learn how to loop through image data using *ngFor, apply lazy loading with IntersectionObserver, and style the layout using Bootstrap 5 (or Angular Material if preferred).
1. Create a New Angular Component (image-gallery.component.ts)
Let’s generate a new component called image-gallery:
ng generate component image-gallery
Now, set up your image array and logic in image-gallery.component.ts:
// image-gallery.component.ts
import { Component, AfterViewInit, ElementRef } from '@angular/core';
@Component({
selector: 'app-image-gallery',
templateUrl: './image-gallery.component.html',
styleUrls: ['./image-gallery.component.css']
})
export class ImageGalleryComponent implements AfterViewInit {
images = [
{ src: 'https://picsum.photos/id/1018/600/400', alt: 'Image 1' },
{ src: 'https://picsum.photos/id/1025/600/400', alt: 'Image 2' },
{ src: 'https://picsum.photos/id/1035/600/400', alt: 'Image 3' },
{ src: 'https://picsum.photos/id/1043/600/400', alt: 'Image 4' }
];
constructor(private el: ElementRef) {}
ngAfterViewInit(): void {
const lazyImages: NodeListOf<HTMLImageElement> = this.el.nativeElement.querySelectorAll('.lazy-image');
if (!('IntersectionObserver' in window)) {
lazyImages.forEach(img => {
img.src = img.dataset.src!;
img.onload = () => img.classList.add('loaded');
});
return;
}
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target as HTMLImageElement;
img.src = img.dataset.src!;
img.onload = () => img.classList.add('loaded');
observer.unobserve(img);
}
});
});
lazyImages.forEach(img => observer.observe(img));
}
}
Code language: JavaScript (javascript)
2. Use *ngFor to Render Images in Template
In image-gallery.component.html, loop through the image array and bind data:
<div class="container py-5">
<h2 class="text-center mb-4">Angular Lazy Loading Gallery</h2>
<div class="row g-4">
<div class="col-12 col-sm-6 col-md-4 col-lg-3" *ngFor="let image of images">
<img
src="https://via.placeholder.com/600x400?text=Loading..."
[attr.data-src]="image.src"
[alt]="image.alt"
class="img-fluid lazy-image rounded shadow-sm"
>
</div>
</div>
</div>
Code language: JavaScript (javascript)
Note: Use [attr.data-src] to safely bind data-src attribute dynamically.
3. Add IntersectionObserver with Lifecycle Hook
We used the AfterViewInit hook to ensure the DOM is ready before accessing image elements. The IntersectionObserver tracks .lazy-image elements and swaps data-src with src when they come into view.
4. Style Using Bootstrap Classes or Angular Material (Optional)
You can continue using Bootstrap as shown above, or if you’re using Angular Material, wrap each image in a mat-card like so:
<mat-card *ngFor="let image of images" class="mat-elevation-z2">
<img
mat-card-image
src="https://via.placeholder.com/600x400?text=Loading..."
[attr.data-src]="image.src"
[alt]="image.alt"
class="lazy-image"
>
</mat-card>
Code language: JavaScript (javascript)
In that case, make sure to import MatCardModule in your module.
And that’s it — you’ve now implemented a modular, reactive lazy loading image gallery in Angular, fully styled and optimized.
Implement Lazy Load Gallery in React
React’s component-driven structure makes it easy to encapsulate lazy loading logic and reuse it across projects. We’ll use useRef and useEffect to set up IntersectionObserver and dynamically load images from a JSON array or props.
1. Create a LazyImageGallery Component
// LazyImageGallery.jsx
import React, { useEffect, useRef } from "react";
import "bootstrap/dist/css/bootstrap.min.css";
const LazyImageGallery = ({ images }) => {
const galleryRef = useRef(null);
useEffect(() => {
const lazyImages = galleryRef.current.querySelectorAll(".lazy-image");
if (!("IntersectionObserver" in window)) {
lazyImages.forEach(img => {
img.src = img.dataset.src;
img.onload = () => img.classList.add("loaded");
});
return;
}
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.onload = () => img.classList.add("loaded");
obs.unobserve(img);
}
});
});
lazyImages.forEach(img => observer.observe(img));
return () => observer.disconnect();
}, []);
return (
<div className="container py-5" ref={galleryRef}>
<h2 className="text-center mb-4">React Lazy Loading Gallery</h2>
<div className="row g-4">
{images.map((image, index) => (
<div className="col-12 col-sm-6 col-md-4 col-lg-3" key={index}>
<img
src="https://via.placeholder.com/600x400?text=Loading..."
data-src={image.src}
alt={image.alt}
className="img-fluid lazy-image rounded shadow-sm"
/>
</div>
))}
</div>
</div>
);
};
export default LazyImageGallery;
Code language: JavaScript (javascript)
2. Load Images Dynamically via Props or JSON
You can pass image data as props from a parent component:
// App.jsx
import React from "react";
import LazyImageGallery from "./LazyImageGallery";
const images = [
{ src: "https://picsum.photos/id/1018/600/400", alt: "Image 1" },
{ src: "https://picsum.photos/id/1025/600/400", alt: "Image 2" },
{ src: "https://picsum.photos/id/1035/600/400", alt: "Image 3" },
{ src: "https://picsum.photos/id/1043/600/400", alt: "Image 4" }
];
function App() {
return <LazyImageGallery images={images} />;
}
export default App;
Code language: JavaScript (javascript)
3. Add Styling for Fade-in Effect
Create a style.css (or include in your main CSS) for the smooth transition:
.lazy-image {
opacity: 0;
transition: opacity 0.5s ease-in-out;
}
.lazy-image.loaded {
opacity: 1;
}
Code language: CSS (css)
4. Use Bootstrap or Tailwind for Styling
We’ve used Bootstrap’s grid system here. If you prefer Tailwind, you can swap Bootstrap classes for Tailwind equivalents:
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{/* images */}
</div>
Code language: HTML, XML (xml)
With this setup, you now have a fully functional React lazy loading gallery that’s modular, reusable, and styled with your choice of CSS framework.
Implement Lazy Load Gallery in Vue
Vue’s declarative rendering and directives make it straightforward to create a lazy loading image gallery. We’ll build a reusable LazyImageGallery.vue component, use v-for to loop through images, and apply IntersectionObserver inside Vue’s lifecycle hooks.
1. Create LazyImageGallery.vue with v-for
<template>
<div class="container py-5" ref="galleryRef">
<h2 class="text-center mb-4">Vue Lazy Loading Gallery</h2>
<div class="row g-4">
<div
class="col-12 col-sm-6 col-md-4 col-lg-3"
v-for="(image, index) in images"
:key="index"
>
<img
src="https://via.placeholder.com/600x400?text=Loading..."
:data-src="image.src"
:alt="image.alt"
class="img-fluid lazy-image rounded shadow-sm"
>
</div>
</div>
</div>
</template>
<script>
export default {
name: "LazyImageGallery",
data() {
return {
images: [
{ src: "https://picsum.photos/id/1018/600/400", alt: "Image 1" },
{ src: "https://picsum.photos/id/1025/600/400", alt: "Image 2" },
{ src: "https://picsum.photos/id/1035/600/400", alt: "Image 3" },
{ src: "https://picsum.photos/id/1043/600/400", alt: "Image 4" }
]
};
},
mounted() {
const lazyImages = this.$refs.galleryRef.querySelectorAll(".lazy-image");
if (!("IntersectionObserver" in window)) {
lazyImages.forEach(img => {
img.src = img.dataset.src;
img.onload = () => img.classList.add("loaded");
});
return;
}
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.onload = () => img.classList.add("loaded");
obs.unobserve(img);
}
});
});
lazyImages.forEach(img => observer.observe(img));
}
};
</script>
<style scoped>
.lazy-image {
opacity: 0;
transition: opacity 0.5s ease-in-out;
}
.lazy-image.loaded {
opacity: 1;
}
</style>
Code language: HTML, XML (xml)
2. Using Local or External Image Sources
You can replace the URLs in the images array with:
- Local images stored in
src/assets/ - External images from services like Picsum Photos, Unsplash, or your own CDN
Example for local images:
images: [
{ src: require("@/assets/photo1.jpg"), alt: "Local Image 1" },
{ src: require("@/assets/photo2.jpg"), alt: "Local Image 2" }
]
Code language: JavaScript (javascript)
3. Optional: Use a Third-Party Lazy Loading Plugin for Vue
If you want even less code in your component, you can use a dedicated lazy loading plugin such as:
- Vue-Lazyload (Vue 2 & Vue 3 support)
- Vueuse UseIntersectionObserver
Example with Vue-Lazyload:
npm install vue-lazyload
// main.js
import { createApp } from "vue";
import App from "./App.vue";
import VueLazyLoad from "vue-lazyload";
createApp(App)
.use(VueLazyLoad, {
loading: "https://via.placeholder.com/600x400?text=Loading..."
})
.mount("#app");
Code language: JavaScript (javascript)
<!-- In your component -->
<img v-lazy="image.src" :alt="image.alt" class="img-fluid rounded shadow-sm">
Code language: HTML, XML (xml)
With this, you now have the Vue version of the lazy loading image gallery, keeping the same behavior and design as our HTML, Angular, and React versions.
Performance Testing & Optimization Tips
Lazy loading can dramatically improve perceived performance, but it’s important to test and quantify the benefits. This ensures your implementation is not only working correctly but also providing the intended speed and UX improvements.
1. Use Lighthouse or Chrome DevTools to Measure Load Time
Lighthouse is built into Chrome DevTools and provides detailed performance metrics.
Steps:
- Open your gallery page in Google Chrome.
- Press
F12or right-click → Inspect. - Go to the Lighthouse tab.
- Select the Performance category and run the audit.
- Check:
- First Contentful Paint (FCP)
- Largest Contentful Paint (LCP)
- Total Blocking Time (TBT)
- Cumulative Layout Shift (CLS)
A well-implemented lazy loading setup should improve FCP and LCP scores, especially for image-heavy pages.
2. Compare Lazy Loading vs. No Lazy Loading
To see the impact:
- Create two versions of the gallery — one with lazy loading and one without.
- Measure page load times and resource sizes in Chrome DevTools Network panel.
- On the lazy loading version:
- Fewer initial image requests should be visible.
- Initial page load size should be smaller.
- Scroll through the page and watch new image requests appear only when images enter the viewport.
This direct comparison highlights the performance gains and bandwidth savings.
3. Compress Images and Use Modern Formats (WebP, AVIF)
Lazy loading helps, but large, unoptimized images can still slow down loading once they’re in view. You can make your gallery even faster by:
- Compressing Images:
- Use tools like TinyPNG or ImageOptim to reduce file size without visible quality loss.
- Using Modern Formats:
- WebP offers ~25–35% smaller file sizes than JPEG/PNG.
- AVIF offers even better compression and quality retention.
- Example usage:
<picture> <source srcset="images/photo1.avif" type="image/avif"> <source srcset="images/photo1.webp" type="image/webp"> <img src="images/photo1.jpg" alt="Photo 1" loading="lazy"> </picture>
- Responsive Images:
- Use
srcsetandsizesto serve appropriately sized images for different devices.
- Use
Optimizing image formats in combination with lazy loading can make your galleries blazing fast while maintaining high visual quality.
With these testing and optimization steps, you can ensure that your lazy loading gallery not only works but delivers measurable, real-world performance improvements.
Final Thoughts & Bonus Resources
Recap: Why Lazy Loading Matters
Lazy loading is a simple yet powerful technique to improve page speed, reduce bandwidth usage, and enhance user experience — especially for image-heavy pages like galleries, product listings, or blogs. By loading only the images that users are about to see, you reduce the initial load burden and keep interactions smooth across devices and network conditions.
In this guide, we explored lazy loading:
- In pure HTML5, CSS3, and JavaScript
- Enhanced with Bootstrap 5
- Implemented in Angular, React, and Vue
No matter which stack you use, the core principle remains the same: load what’s needed, when it’s needed.
Resources: Documentation, Tools, and Plugins
Here are some helpful resources to dive deeper:
- MDN Docs – Lazy loading: https://developer.mozilla.org/docs/Web/Performance/Lazy_loading
- Bootstrap Documentation: https://getbootstrap.com/docs/5.3
- Angular Docs: https://angular.io/docs
- React Docs: https://react.dev
- Vue Docs: https://vuejs.org/guide
- Image Optimization Tools:
- Plugins:
- Vue Lazyload: https://github.com/hilongjw/vue-lazyload
- React Lazy Load Image Component: https://github.com/Aljullu/react-lazy-load-image-component
Demo Links (Optional)
You can check out the working demos here:
- CodePen HTML + JS + Bootstrap Demo: (Add your link here)
- GitHub Repo with Multi-Framework Examples: (Add your repo link here)
Share Your Thoughts
I’d love to hear your feedback.
- Did this tutorial help you build a faster, better gallery?
- Which framework did you use for your implementation?
- Have you found other optimization tricks for image-heavy pages?
Drop a comment below or reach out — your ideas and experiences could help others improve their projects too.
Did you enjoy this guide? Share it with your fellow developers so they can build faster, more efficient image galleries too.
Now it’s your turn — try creating your own lazy loading gallery in your preferred stack (HTML, Angular, React, or Vue) and see how much faster your pages load. If you post your version online, tag me so I can check it out and share it.
For more step-by-step, multi-platform tutorials like this one, consider subscribing to get notified when new guides are published. You’ll get fresh techniques and real-world examples you can apply immediately in your projects.
Ready to Take Your Web Skills to the Next Level?
If you enjoyed building this feedback form, imagine what you could do with the right guidance and a step-by-step course tailored just for you!
Discover more from Prime Inspire
Subscribe to get the latest posts sent to your email.



