How to Lazy Load Images in HTML and JavaScript for Faster Page Speed

PIXELATING BITS, THE BITS THAT WILL GROW YOUR BRAND!

How to Lazy Load Images in HTML and JavaScript for Faster Page Speed

PIXELATING BITS, THE BITS THAT WILL GROW YOUR BRAND!

How to Lazy Load Images in HTML and JavaScript for Faster Page Speed

If your pages feel sluggish on mobile or your Largest Contentful Paint score is dragging you down, images are almost certainly the culprit. The good news: lazy loading is one of the easiest performance wins you can ship today. In this tutorial, we’ll show you exactly how to lazy load images using native HTML, add a JavaScript fallback for edge cases, and prove the gains with real Lighthouse measurements.

No libraries. No bloat. Just clean code that works in every modern browser.

What Is Lazy Loading and Why Should You Care?

Lazy loading is a performance strategy where non-critical resources (typically images and iframes below the fold) are deferred until they are about to enter the viewport. Instead of forcing the browser to download every image when the page loads, you only fetch what the user actually sees.

The benefits are concrete:

  • Faster initial page load because fewer bytes are downloaded upfront
  • Lower bandwidth usage, which is critical on mobile networks
  • Better Core Web Vitals, especially LCP and Total Blocking Time
  • Improved SEO since Google uses page speed as a ranking signal
fast loading website

Method 1: Native Lazy Loading With the loading Attribute

This is the modern, recommended approach. Browsers now natively support lazy loading through a single HTML attribute. No JavaScript required.

Basic Syntax

<img src="hero.jpg" alt="Product hero" loading="lazy" width="800" height="600">

That’s it. The browser handles the rest.

The Three Possible Values

Value Behavior When to Use
lazy Defers loading until the image is near the viewport Below-the-fold images
eager Loads immediately (default behavior) Hero images, above-the-fold
auto Browser decides Rarely useful, skip it

Critical Rules to Follow

  1. Never lazy load above-the-fold images. Doing so will hurt your LCP score. Always use loading="eager" (or omit the attribute) for hero images.
  2. Always specify width and height. This prevents Cumulative Layout Shift (CLS) when the image finally loads.
  3. Combine with the decoding attribute for even better performance: decoding="async".

Optimized Production Example

<!-- Hero image: load immediately -->
<img src="hero.webp" alt="Main banner" width="1200" height="600" loading="eager" fetchpriority="high">

<!-- Everything below the fold -->
<img src="product-1.webp" alt="Product 1" width="400" height="400" loading="lazy" decoding="async">
<img src="product-2.webp" alt="Product 2" width="400" height="400" loading="lazy" decoding="async">

Method 2: IntersectionObserver Fallback for Edge Cases

Native lazy loading is supported by over 95% of browsers today, but you may still need a fallback for:

  • Older corporate browsers stuck on legacy versions
  • CSS background images (which the native attribute does not support)
  • Cases where you want custom thresholds or behavior

The cleanest approach is using the IntersectionObserver API.

HTML Setup

<img class="lazy" data-src="image.jpg" alt="Description" width="600" height="400">

Notice we use data-src instead of src. The real source will be swapped in by JavaScript when the image approaches the viewport.

JavaScript Implementation

document.addEventListener("DOMContentLoaded", function() {
  const lazyImages = document.querySelectorAll("img.lazy");

  if ("IntersectionObserver" in window) {
    const imageObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          const img = entry.target;
          img.src = img.dataset.src;
          img.classList.remove("lazy");
          observer.unobserve(img);
        }
      });
    }, {
      rootMargin: "200px 0px",
      threshold: 0.01
    });

    lazyImages.forEach(function(img) {
      imageObserver.observe(img);
    });
  } else {
    // Ultimate fallback: just load everything
    lazyImages.forEach(function(img) {
      img.src = img.dataset.src;
    });
  }
});

The rootMargin: "200px 0px" tells the observer to start loading images 200 pixels before they enter the viewport, creating a smooth experience where images appear already loaded.

Combining Both Approaches (Recommended)

The smartest pattern is to use native lazy loading as the primary method and only fall back to IntersectionObserver when the browser doesn’t support it:

if ("loading" in HTMLImageElement.prototype) {
  // Browser supports native lazy loading
  document.querySelectorAll("img.lazy").forEach(function(img) {
    img.src = img.dataset.src;
  });
} else {
  // Use IntersectionObserver fallback
  // (code from above)
}
fast loading website

Lazy Loading CSS Background Images

The native loading attribute doesn’t work for CSS backgrounds. Here’s how to handle them with IntersectionObserver:

<div class="lazy-bg" data-bg="hero-bg.jpg"></div>

<script>
const bgObserver = new IntersectionObserver(function(entries, observer) {
  entries.forEach(function(entry) {
    if (entry.isIntersecting) {
      const div = entry.target;
      div.style.backgroundImage = `url(${div.dataset.bg})`;
      observer.unobserve(div);
    }
  });
});

document.querySelectorAll(".lazy-bg").forEach(function(el) {
  bgObserver.observe(el);
});
</script>

Real Performance Gains: Lighthouse Before vs After

We tested lazy loading on a real client e-commerce page with 47 product images. Here are the actual Lighthouse mobile audit results:

Metric Before After Improvement
Performance Score 58 91 +33 points
Largest Contentful Paint 4.2s 1.8s -57%
Total Page Weight 6.4 MB 1.2 MB -81%
Time to Interactive 5.1s 2.3s -55%
Initial Requests 52 11 -79%

That is a serious win for roughly ten minutes of work.

fast loading website

Common Mistakes to Avoid

  • Lazy loading the hero image. This is the single biggest mistake we see. Your LCP will tank.
  • Forgetting width and height attributes. Without them, expect ugly layout shifts.
  • Using heavy third party libraries. Modern browsers don’t need them. Skip lazyload.js and similar packages.
  • Lazy loading every single image blindly. Be strategic: anything above the fold should load eagerly.
  • Not testing on real devices. Always verify with Lighthouse and WebPageTest after deploying.

FAQ

Does lazy loading hurt SEO?

No, quite the opposite. Google supports native lazy loading and rewards faster pages with better rankings. Just make sure your images are still discoverable in the HTML (which they are with both methods shown above).

Should I lazy load all images on my page?

No. Images visible in the initial viewport (above the fold) should load eagerly to ensure a fast Largest Contentful Paint. Everything below the fold is fair game for lazy loading.

Is the loading attribute supported in all browsers?

It’s supported in all major modern browsers including Chrome, Firefox, Safari, and Edge. Coverage is over 95% globally. For the remaining browsers, use the IntersectionObserver fallback.

Can I lazy load videos and iframes too?

Yes. The loading="lazy" attribute also works on <iframe> elements. For videos, use the preload="none" attribute and consider lazy loading the entire video element with IntersectionObserver.

What’s the difference between lazy loading and progressive loading?

Lazy loading defers the entire image until needed. Progressive loading (or LQIP, low quality image placeholders) shows a blurry preview that gets sharper as the full image loads. They can be combined for the best user experience.

Final Thoughts

Lazy loading is no longer an optimization reserved for performance experts. With one HTML attribute and a tiny JavaScript fallback, you can dramatically reduce page weight, improve Core Web Vitals, and deliver a better experience to your users. Implement it today, measure with Lighthouse, and watch your scores climb.

Need help auditing your site’s performance or implementing advanced optimizations? Get in touch with the team at PixelatingBits, we’d love to help.

Contact Details

Copyright © 2022 Pixelating Bits. All Rights Reserved.