Computer screen displaying JavaScript code
Frontend Performance

Mastering Native Image Lazy Loading with Vanilla JavaScript

WebPMagic Avatar WebPMagic Editorial
12 Min Read

In the pursuit of the perfect 100/100 Google Lighthouse score, developers often obsess over minifying CSS and deferring JavaScript. However, they frequently overlook the heaviest assets on the page: images. Eagerly loading every image on a webpage, regardless of whether the user will ever scroll down to see them, is a massive waste of bandwidth and a primary culprit for poor Largest Contentful Paint (LCP) metrics.

Key Takeaways

  • Eager loading heavily penalizes performance and Core Web Vitals.
  • HTML\'s native loading="lazy" attribute resolves most lazy-loading needs effortlessly.
  • Providing explicit image dimensions completely prevents layout shifting (CLS).
  • For legacy support and advanced UI behaviors, the native Intersection Observer API provides highly performant lazy loading.

The Problem with Eager Loading

Historically, browsers were designed to fetch all resources declared in the DOM as quickly as possible. If your e-commerce product page has 50 high-resolution images, the browser opens network requests for all 50 simultaneously. This creates a severe bottleneck. The browser's main thread becomes congested parsing images that are thousands of pixels below the viewport, delaying the rendering of critical above-the-fold content.

This results in a sluggish user experience, higher server costs, and severe penalties in search engine rankings under Google's Core Web Vitals framework. The solution is Lazy Loading.

Phase 1: The Native HTML loading="lazy" Attribute

The easiest, most robust way to implement lazy loading today requires absolutely zero JavaScript. Modern browsers now natively support the loading attribute on <img> and <iframe> tags.

<!-- Native Lazy Loading Example -->
    <img 
        src="optimized-hero-image.webp" 
        alt="Developer coding on laptop"
        width="800" 
        height="600" 
        loading="lazy" 
    />

By adding loading="lazy", you instruct the browser to defer fetching the image until it crosses a calculated distance threshold from the user's viewport.

Crucial: Always Include Explicit Dimensions

Notice the width and height attributes in the code block above. When deferring images, the browser doesn't know the aspect ratio of the image until it downloads. If you omit dimensions, the browser won't allocate space for the image. When the user scrolls down and the image finally loads, the layout will aggressively shift, ruining your Cumulative Layout Shift (CLS) score. Always declare explicit dimensions to reserve the layout space.

Phase 2: The Vanilla JS Intersection Observer API

While native lazy loading is excellent, there are scenarios where you need granular control—such as triggering CSS animations when an image enters the viewport, loading CSS background images, or supporting legacy browsers (like older versions of Safari). For this, we bypass heavy libraries like jQuery and use pure Vanilla JavaScript via the Intersection Observer API.

The Intersection Observer provides a highly performant way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport. It does not run on the main thread, making it incredibly fast.

Step 1: Update Your Markup

Instead of putting the image URL in the src attribute (which triggers an immediate download), we place it in a custom data-src attribute. We use a tiny, 10px placeholder for the initial load.

<img 
        class="lazy-image blur-up" 
        src="tiny-placeholder-10px.jpg" 
        data-src="high-res-image.webp" 
        alt="Detailed architecture" 
        width="1200" 
        height="800" 
    />

Step 2: The Vanilla JS Logic

Here is the pure JavaScript code to observe these elements and swap the src only when they approach the viewport:

document.addEventListener("DOMContentLoaded", function() {
        let lazyImages = [].slice.call(document.querySelectorAll("img.lazy-image"));
    
        if ("IntersectionObserver" in window) {
            let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
                entries.forEach(function(entry) {
                    // If the image is in the viewport
                    if (entry.isIntersecting) {
                        let lazyImage = entry.target;
                        
                        // Swap the data-src to the actual src
                        lazyImage.src = lazyImage.dataset.src;
                        
                        // Optional: remove a blur class for a smooth CSS transition
                        lazyImage.classList.remove("blur-up");
                        lazyImage.classList.add("loaded");
                        
                        // Stop observing once loaded to save memory
                        lazyImageObserver.unobserve(lazyImage);
                    }
                });
            }, {
                // Load the image 50px before it enters the screen
                rootMargin: "0px 0px 50px 0px"
            });
    
            lazyImages.forEach(function(lazyImage) {
                lazyImageObserver.observe(lazyImage);
            });
        } else {
            // Fallback for extremely old browsers
            lazyImages.forEach(function(lazyImage) {
                lazyImage.src = lazyImage.dataset.src;
            });
        }
    });

The WebP Connection

Lazy loading ensures images are only requested when needed. But when they are requested, they still need to be lightweight. Combining lazy loading with WebP image optimization creates the ultimate performance duo. By running your standard JPEGs through a tool like WebPMagic, you shrink the payload by 30-40%. Combine that with lazy loading, and your initial page load payload drops to almost zero, guaranteeing top-tier Web Vitals scores and happier visitors.

Ready to shrink your payloads?

Before you lazy load your images, ensure they are converted to the modern WebP format. Use our secure, browser-based optimizer directly from your browser.

Optimize Images Now
WebPMagic Editorial

WebPMagic Editorial Team

Our editorial team consists of seasoned full-stack developers and SEO specialists dedicated to making the web faster. We share practical, code-first solutions to help you ace your Core Web Vitals audits.