Add Lazy‑Load Images Without a Library: A Reusable JavaScript Snippet for Fast Pages

If you’ve ever watched a page crawl while big hero images load, you know the feeling – a little bit of panic mixed with a lot of wasted bandwidth. The good news? You can fix that with just a few lines of vanilla JavaScript. No extra packages, no heavy dependencies, just plain code you can drop into any project. At JS Snippet Hub we love keeping things lightweight, and this snippet is a perfect example of that philosophy.

Why Lazy‑Loading Matters Right Now

Modern sites are full of high‑resolution pictures. A typical blog post can easily have three or four images that together weigh a few megabytes. On a slow mobile connection that means users stare at a blank screen while the browser fights for every byte. Lazy‑loading solves the problem by only loading images when they are about to appear in the viewport. The result is faster first‑paint times, lower data usage, and happier visitors.

The Core Idea in Plain English

Think of lazy‑loading as a “wait‑until‑you‑need‑it” rule. Instead of telling the browser to fetch every <img> as soon as the HTML is parsed, we give it a placeholder and a signal: “When this picture scrolls into view, go ahead and load it.” The browser then requests the real image file only at that moment.

Two browser features make this easy:

  1. data- attributes – custom attributes that let us store the real image URL without triggering a download.
  2. IntersectionObserver – an API that watches an element and tells us when it crosses a threshold (like entering the viewport).

If a browser doesn’t support IntersectionObserver, we fall back to a simple scroll listener. That keeps the snippet compatible with older browsers while still being fast on modern ones.

Step‑By‑Step Walkthrough

1. Mark Up Your Images

Replace the normal src attribute with a data-src. Add a tiny transparent placeholder (or a low‑quality image) to keep the layout stable.

<img class="lazy" data-src="hero.jpg" src="placeholder.png" alt="Hero image">
<img class="lazy" data-src="gallery1.jpg" src="placeholder.png" alt="Gallery photo 1">

The class="lazy" is just a hook for our script. The placeholder can be a 1×1 pixel GIF or a blurred version of the real picture.

2. The JavaScript Snippet

Copy the code below into a file called lazyload.js or drop it into a <script> tag at the bottom of your page.

(function () {
  // Helper: load the real image
  function loadImage(img) {
    var src = img.getAttribute('data-src');
    if (!src) return;
    img.src = src;
    img.removeAttribute('data-src');
    img.classList.remove('lazy');
  }

  // If IntersectionObserver is available, use it
  if ('IntersectionObserver' in window) {
    var observer = new IntersectionObserver(function (entries, obs) {
      entries.forEach(function (entry) {
        if (entry.isIntersecting) {
          loadImage(entry.target);
          obs.unobserve(entry.target);
        }
      });
    });

    // Observe each lazy image
    var lazyImages = document.querySelectorAll('img.lazy[data-src]');
    lazyImages.forEach(function (img) {
      observer.observe(img);
    });
  } else {
    // Fallback for older browsers: check on scroll and resize
    var lazyImages = Array.prototype.slice.call(
      document.querySelectorAll('img.lazy[data-src]')
    );

    function onScroll() {
      if (lazyImages.length === 0) {
        window.removeEventListener('scroll', onScroll);
        window.removeEventListener('resize', onScroll);
        return;
      }

      var viewportHeight = window.innerHeight;
      lazyImages = lazyImages.filter(function (img) {
        var rect = img.getBoundingClientRect();
        if (rect.top < viewportHeight + 100) { // 100px buffer
          loadImage(img);
          return false; // remove from array
        }
        return true;
      });
    }

    window.addEventListener('scroll', onScroll);
    window.addEventListener('resize', onScroll);
    // Run once in case images are already in view
    onScroll();
  }
})();

How It Works

  • loadImage swaps the placeholder with the real URL stored in data-src. It also cleans up the class and attribute so the image won’t be processed again.
  • IntersectionObserver creates an observer that watches each lazy image. When an image becomes visible (isIntersecting), we load it and stop observing it.
  • Fallback builds a simple list of images and checks their position on every scroll or resize event. A small 100‑pixel buffer makes the loading feel smooth.

3. Fine‑Tuning for Your Site

  • Thresholds – If you want images to load a bit earlier, change the 100 in the fallback or add { rootMargin: '200px' } to the observer options.
  • Fade‑In Effect – Add a CSS rule like .lazy { opacity: 0; transition: opacity .3s; } img:not(.lazy) { opacity: 1; } to make images appear gently.
  • Responsive Images – You can still use srcset and sizes. Keep the real srcset in a data-srcset attribute and modify loadImage to copy those values when the image is ready.

Real‑World Example from My Blog

When I first added this snippet to JS Snippet Hub, the homepage dropped from a 3.2 s load time to 1.8 s on a typical 3G connection. The biggest win was the hero banner – it now only loads after the user scrolls past the intro text. I measured the change with Chrome’s Lighthouse and the “Avoid large layout shifts” score went up as well, because the placeholder kept the space reserved.

Common Pitfalls and How to Dodge Them

ProblemWhy It HappensFix
Images still load immediatelyYou left a src attribute pointing to the real file.Remove src or replace it with a tiny placeholder.
Images never appearThe observer isn’t attached because the script runs before the DOM is ready.Place the script at the end of <body> or wrap it in DOMContentLoaded.
Layout jumps on loadPlaceholder size differs from real image size.Use CSS to set a fixed height or aspect‑ratio on the <img> container.

Wrap‑Up Thoughts

Lazy‑loading is a small trick with a big payoff. By using just a handful of lines, you keep your pages fast, your users happy, and your codebase clean. The snippet above is deliberately simple – you can copy it, tweak the thresholds, add a fade‑in, or even extend it to background images later. The key is to stay in control of what gets downloaded and when.

At JS Snippet Hub we try to share tools that feel like a natural extension of the language, not a heavy add‑on. This lazy‑load snippet fits that bill perfectly: pure JavaScript, no extra weight, and easy to drop into any project.

Happy coding, and may your pages load as fast as your coffee brews.

Reactions