Skip to content
tiny·pixel·kit
← All articles
PerformanceDec 12, 2025 · 5 min read

Lazy loading images — the complete guide

Native lazy loading, intersection observers, and common pitfalls. Everything you need to defer off-screen images correctly.

The Tiny Pixel Kit Team
Field notes from shipping image tools.

Lazy loading defers the download of off-screen images until the user scrolls near them. It's one of the simplest performance wins available — and it's built into every modern browser.

Native lazy loading

HTML has a loading attribute that handles lazy loading without any JavaScript:

<img src="photo.webp" loading="lazy" width="800" height="600" alt="..." />

That's it. The browser decides when to start downloading based on the user's scroll position and network conditions.

Browser support: Chrome, Firefox, Edge, Safari — all modern browsers. No polyfill needed in 2026.

When to lazy load (and when not to)

Do lazy load:

  • Images below the fold (not visible on initial page load)
  • Images in long content pages (blog posts, product listings)
  • Thumbnails in image galleries
  • Background images loaded via CSS (use Intersection Observer)

Don't lazy load:

  • The LCP image (hero image, main product photo) — lazy loading delays it
  • Images above the fold that are immediately visible
  • Critical UI elements like logos

The LCP trap

This is the most common lazy loading mistake. Adding loading="lazy" to your hero image tells the browser to deprioritise it — exactly the opposite of what you want for your Largest Contentful Paint metric.

Instead, preload the LCP image:

<!-- In <head> -->
<link rel="preload" as="image" href="/hero.avif" type="image/avif" />

<!-- In <body> -->
<img src="/hero.avif" width="1200" height="600" alt="..." fetchpriority="high" />

Note: fetchpriority="high" is a newer attribute that tells the browser to prioritise this resource.

Width and height are required

For lazy loading to work without causing layout shift, you must set width and height on every image. This lets the browser reserve the correct space before the image loads.

<!-- Good: space is reserved -->
<img src="photo.webp" loading="lazy" width="800" height="600" alt="..." />

<!-- Bad: causes layout shift when image loads -->
<img src="photo.webp" loading="lazy" alt="..." />

Without dimensions, the image has zero height until it loads, then suddenly pushes content down — exactly the layout shift that CLS penalises.

Intersection Observer (for advanced cases)

If you need more control — custom thresholds, animation triggers, or background image lazy loading — use the Intersection Observer API:

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

document.querySelectorAll('img[data-src]').forEach((img) => {
  observer.observe(img);
});

The rootMargin: '200px' starts loading images 200px before they enter the viewport, so they're ready by the time the user scrolls to them.

The decoding attribute

Pair loading="lazy" with decoding="async" for maximum performance:

<img src="photo.webp" loading="lazy" decoding="async" width="800" height="600" alt="..." />

decoding="async" tells the browser to decode the image off the main thread, preventing decode jank.

Measuring the impact

Before and after adding lazy loading, measure:

  1. Total page weight (Network tab → transferred size)
  2. LCP (Lighthouse → Performance)
  3. CLS (must not increase — set width/height!)
  4. Number of requests on initial load (should decrease)

A blog post with 15 images might go from 3 MB on initial load to 400 KB — with the rest loading on demand as the user scrolls.

Combine with compression

Lazy loading reduces when images load, but compression reduces how much data each image needs. Use both together for the best performance:

  1. Compress all images to AVIF/WebP
  2. Resize to 2× CSS display dimensions
  3. Add loading="lazy" to below-fold images
  4. Add fetchpriority="high" to the LCP image
more articles

Keep reading.

We only load privacy-safe analytics and ads after your choice. Our tools run locally and never upload your images. Privacy policy