lazy loading

Native Lazy Loading — Live Demos

5 interactive examples

The loading threshold — scroll to trigger lazy loads

Scroll inside the viewport box below. Images start loading before they're visible — at the browser's distance threshold — so they're ready when you reach them. Watch the status badges.

~1,250px
Fast connection (4G) threshold
~2,500px
Slow connection (3G) threshold
97.5%
Loaded within 10ms of visible (4G)
▼ Scroll down — viewport (loads images ~1 block ahead)
--:--:--Scroll the box above — images load just before entering view

Silent Failure #1 — lazy-loading the hero delays LCP

Both pages have the same hero image. Left lazy-loads it; right uses fetchpriority="high". Click "Load page" to simulate the render sequence and watch when LCP fires.

❌ <img loading="lazy"> on hero
hero (deferred)
✅ fetchpriority="high"
hero (priority)
❌ <img src="hero.webp" loading="lazy"> // LCP ~2.4s ✅ <img src="hero.webp" fetchpriority="high"> // LCP ~1.0s // 200-800ms improvement on image-heavy pages
--:--:--Click "Load both pages" to compare LCP timing

Silent Failure #2 — missing dimensions cause layout shift

Both columns lazy-load an image mid-content. Left has no width/height; right has them. Click "Load image" and watch the text below jump on the left (CLS) but stay still on the right.

❌ No width/height
CLS: 0.00
✅ width="1200" height="630"
CLS: 0.00
reserved space
--:--:--Click "Load the lazy image" — left column shifts, right stays put

Silent Failure #3 — loading="lazy" does nothing on backgrounds

Native lazy loading only works on <img> and <iframe>. A CSS background-image loads immediately, no matter what. Here's the failure and the three fixes.

❌ CSS background — loads eagerly
.hero { background-image: url(...) }
Loaded immediately on render
loading="lazy" has zero effect here
✅ <img> + object-fit: cover
<img loading="lazy" style="object-fit:cover">
Lazy-loads correctly
Same visual, real lazy loading

The three fixes for background lazy-loading

Fix A — Use <img> instead
Position an <img> with object-fit:cover behind content. Gets native lazy loading.
Fix B — IntersectionObserver
Set background-image from data-bg only when the element nears the viewport.
Fix C — content-visibility: auto
Skips all rendering work for an offscreen section until it's needed.
--:--:--CSS backgrounds can't use loading="lazy" — use one of the three fixes

The facade pattern — lazy isn't enough for heavy embeds

A YouTube iframe ships 800KB+ of JS even when lazy-loaded. The facade shows a thumbnail and only loads the real iframe on click. Click each to compare the payload.

❌ Lazy iframe — loads 800KB when near viewport
▶ iframe loaded 800KB JS downloaded automatically
Payload:
800KB
✅ Facade — 0KB until clicked
thumbnail.jpg (12KB)
Payload:
12KB
// Facade: click loads the real iframe function loadEmbed(facade) { const iframe = document.createElement('iframe'); iframe.src = `https://youtube.com/embed/${id}?autoplay=1`; facade.replaceWith(iframe); // 800KB loads only NOW }
--:--:--Click the thumbnail — the 800KB iframe loads only on interaction
Read the tutorial