How to Make a Lazy Loading Image Gallery

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).

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:

FeatureLazy LoadingProgressive LoadingInfinite Scrolling
PurposeDelay loading offscreen imagesLoad a low-res version firstLoad more content as user scrolls
When to UseImage galleries, blogs, product listingsPhoto-heavy pages where loading feedback mattersSocial feeds, news apps, long lists
Tech Usedloading="lazy", IntersectionObserverBlurred image previews, LQIPJS listeners for scroll or IntersectionObserver
User ControlFull control with scrollImmediate preview, clearer on loadContent 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.jpg is a low-resolution image or blank image shown initially.
  • data-src stores the actual image path that will be loaded via JavaScript later.
  • The lazy-image class 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 + minmax ensures the gallery adapts to various screen sizes.
  • gap defines spacing between images.
  • border-radius and transition give 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-image elements.
  • When an image is in view (entry.isIntersecting), we:
    • Replace src with data-src
    • Add a .loaded class after it’s fully loaded
    • Unobserve the image to stop checking it

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:

  • container ensures consistent horizontal padding.
  • row g-4 creates a flex grid with responsive spacing (g-4 = gap: 1.5rem).
  • col-12 col-sm-6 col-md-4 col-lg-3 ensures 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 images
  • shadow-sm or shadow – Adds soft drop shadows
  • mb-3 or p-2 – Controls spacing
  • text-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 placeholder
  • data-src="..." for the actual image
  • class="lazy-image" for the script to detect
  • img-fluid for 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:
    Replace images/photo1.jpg with: 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:

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:

  1. Open your gallery page in Google Chrome.
  2. Press F12 or right-click → Inspect.
  3. Go to the Lighthouse tab.
  4. Select the Performance category and run the audit.
  5. 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:

  1. Create two versions of the gallery — one with lazy loading and one without.
  2. Measure page load times and resource sizes in Chrome DevTools Network panel.
  3. On the lazy loading version:
    • Fewer initial image requests should be visible.
    • Initial page load size should be smaller.
  4. 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 srcset and sizes to serve appropriately sized images for different devices.

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:

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.


Discover more from Prime Inspire

Subscribe to get the latest posts sent to your email.

We’d love to hear your thoughts! Share your ideas below 💡

Scroll to Top

Discover more from Prime Inspire

Subscribe now to keep reading and get access to the full archive.

Continue reading