How to Lazy Load Images Using Native JavaScript

Santaji GadeSEOCore Web Vitals2 weeks ago36 Views

lazy load images

A product page with 60 photos doesn't need to load all 60 upfront. Here's working code for three ways to lazy load images native attribute, IntersectionObserver, and CSS backgrounds plus the one mistake that hurts LCP.

Technical SEO Lazy Load Images JavaScript Performance

A product page with 60 photos does not need to download all 60 the moment someone lands on it. Visitors typically see three or four before scrolling. Native JavaScript can lazy load images with a single HTML attribute, or with full custom control through the IntersectionObserver API, and this guide gives you working code for both.

Lazy loading images with native JavaScript means deferring image downloads until they are actually close to entering the viewport, using either the browser's built-in loading="lazy" attribute or the IntersectionObserver API for cases the native attribute cannot handle, like CSS background images.

We covered the strategy and SEO implications in our lazy loading images guide. This article is the implementation half: the actual code, working today, in three different approaches.

Advertisement
Advertisement
95%

of global browser traffic supports the native loading="lazy" attribute directly

1

HTML attribute is all that's needed for the simplest lazy load images implementation

0

times you should ever lazy load your page's LCP or hero image

Browser Support: Why Native Alone Is Usually Enough

95% Native Support
3.5%
Supports loading="lazy" natively IntersectionObserver only, older Safari Neither supported, legacy browsers

Method 1: Native Lazy Loading in One Attribute

This is the simplest way to lazy load images, requiring no JavaScript at all for the majority of visitors.

<img src="product-photo.jpg"
     width="600" height="400"
     loading="lazy"
     alt="Blue ceramic coffee mug on wooden table">

width and height are required to prevent layout shift once the image actually loads

Critical Warning

Never add loading="lazy" to your LCP or hero image. Doing so delays the browser's largest paint by 200-500ms and is flagged directly by Lighthouse as a performance anti-pattern.

Method 2: IntersectionObserver for Full Control

According to DebugBear's guide to lazy loading with IntersectionObserver, this native browser API detects viewport intersection without the performance cost of manual scroll event listeners.

const lazyImages = document.querySelectorAll('img[data-src]');

const imageObserver = new IntersectionObserver((entries, observer) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      img.removeAttribute('data-src');
      observer.unobserve(img);
    }
  });
}, { rootMargin: '200px 0px' });

lazyImages.forEach((img) => imageObserver.observe(img));

rootMargin loads images 200px before they reach the visible viewport, avoiding a visible pop-in

<img data-src="product-photo.jpg"
     src="placeholder-tiny.jpg"
     width="600" height="400"
     alt="Blue ceramic coffee mug on wooden table">

Matching HTML: the real image URL sits in data-src until the observer swaps it in

Method 3: Lazy Loading CSS Background Images

According to PageSpeed Matters' complete 2026 lazy loading guide, the native loading attribute does not apply to CSS background images at all, since they are not standard img elements.

document.querySelectorAll('.lazy-bg').forEach((el) => {
  const bgObserver = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        entry.target.classList.add('bg-loaded');
        entry.unobserve?.(entry.target) ?? bgObserver.unobserve(entry.target);
      }
    });
  });
  bgObserver.observe(el);
});

Adds a class that triggers the actual background-image URL, defined separately in CSS

/* CSS: background-image only applies once .bg-loaded is added */
.lazy-bg.bg-loaded {
  background-image: url('hero-banner.jpg');
}

The class-swap pattern for background images referenced above

Advertisement
Advertisement

Should You Use a Feature-Detection Fallback?

For most sites, no. Tap through both scenarios to see why.

According to PageSpeed Checklist's guide to native lazy image loading, roughly 95% of traffic already supports loading="lazy" natively, making a JavaScript fallback unnecessary complexity for most general-audience sites.

A site with a meaningful share of older Safari traffic can detect support and apply IntersectionObserver only where native support is missing, avoiding unnecessary JavaScript execution everywhere else.

if ('loading' in HTMLImageElement.prototype) {
  // Native lazy loading is supported, do nothing extra
} else if ('IntersectionObserver' in window) {
  // Fall back to the IntersectionObserver pattern from Method 2
}

Feature detection: only runs the JavaScript fallback where native support is actually missing

Lazy Load Decision Helper

Select the type of image to get the right technique for it.

Lazy Load Decision Helper

Not every image should be lazy loaded the same way, or at all

Do NOT lazy load this. Use fetchpriority="high" instead to speed it up.

Why You'd Choose IntersectionObserver Over Native

According to Fasal Engineering's step-by-step guide to IntersectionObserver, the API uses the browser's compositor thread for intersection calculations, avoiding the main-thread cost that manual scroll event listeners used to require before this became possible to lazy load images natively at all.

According to Dean Hume's guide to lazy loading images using Intersection Observer, a graceful fallback for browsers lacking IntersectionObserver support simply loads all images immediately, rather than leaving them permanently hidden.

Real-World Impact of Lazy Loading Images Correctly

According to VitalsFixer's complete native lazy loading guide, a typical product page loading 60 images upfront, when only 3 or 4 are ever seen without scrolling, wastes significant bandwidth that lazy loading images correctly eliminates entirely.

Advertisement
Advertisement

Preventing Layout Shift From Lazy-Loaded Images

RuleWhy It Matters
Always set width and heightReserves space before the image loads, preventing CLS
Never lazy load the LCP elementAdds 200-500ms delay to your largest paint metric
Use a low-quality placeholder for Method 2Avoids a jarring blank-to-image pop-in effect
Add fetchpriority="high" to hero imagesTells the browser to fetch it before other resources
Test with Lighthouse after implementationFlags any accidental lazy loading of above-fold content

Testing Your Lazy Loading Setup

According to web.dev's official guide to browser-level image lazy loading, running Lighthouse after implementation confirms two things at once: that below-fold images are correctly deferred, and that no above-fold or LCP image was accidentally caught by the same loading="lazy" attribute.

According to Divotion's guide to lazy loading images with IntersectionObserver, testing on an actual mobile connection, not just desktop Chrome DevTools throttling, catches real-world lazy loading images behavior that a fast local connection can mask entirely.

FAQs on Lazy Loading Images With Native JavaScript

Do I need a library to lazy load images?
No. The native loading="lazy" attribute and the IntersectionObserver API are both built into modern browsers, requiring no external JavaScript library for either approach.
When should I use IntersectionObserver instead of the native attribute?
Use IntersectionObserver for CSS background images, custom loading animations, or when you need more control than the native attribute provides, such as a specific rootMargin buffer distance.
Should I lazy load my hero image?
No. Never lazy load an image that is the page's LCP element. Doing so delays the largest paint and is directly flagged by Lighthouse as a performance mistake.
Do lazy-loaded images still need alt text?
Yes. Lazy loading is unrelated to image SEO. Every image still needs descriptive, keyword-natural alt text regardless of whether it loads immediately or later.
Why do my lazy-loaded images cause layout shift?
This happens when width and height attributes are missing. Without them, the browser cannot reserve space for the image before it loads, causing surrounding content to jump once it appears.
Can I lazy load iframes the same way as images?
The native loading="lazy" attribute also works on iframe elements directly. For heavy embeds like YouTube or Google Maps, a facade pattern showing a thumbnail until clicked often saves even more bandwidth.

> what_we_learn_today.log

[OK]

Native loading="lazy" covers roughly 95% of traffic with one HTML attribute

[OK]

IntersectionObserver handles CSS backgrounds and custom control needs

[OK]

Never lazy load the LCP or hero image; it delays your biggest paint metric

[OK]

width and height attributes prevent layout shift once images load

[OK]

A feature-detection fallback is optional, useful mainly for older Safari

[OK]

Lazy-loaded images still need full, descriptive alt text

0 Votes: 0 Upvotes, 0 Downvotes (0 Points)

Leave a reply

Loading Next Post...
Search
Popular Now
Loading

Signing-in 3 seconds...

Signing-up 3 seconds...