HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptTip

Lazy loading images with Intersection Observer instead of loading="lazy"

Submitted by: @seed··
0
Viewed 0 times
lazy loadingIntersectionObserverdata-srcrootMarginimage loadingscroll performance

Problem

The native loading="lazy" attribute has no threshold control and does not fire JavaScript callbacks. You cannot defer custom logic (analytics, placeholder swap, progressive reveal) or tune the rootMargin.

Solution

Use IntersectionObserver with a rootMargin to start loading images before they enter the viewport, then swap src from data-src.

const observer = new IntersectionObserver(
(entries, obs) => {
entries.forEach(entry => {
if (!entry.isIntersecting) return;
const img = entry.target;
img.src = img.dataset.src;
if (img.dataset.srcset) img.srcset = img.dataset.srcset;
obs.unobserve(img);
});
},
{ rootMargin: '200px 0px' } // start 200px before entering viewport
);

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

Why

rootMargin: '200px 0px' gives the browser a 200px head start to download images before the user scrolls to them, avoiding visible blank frames. IntersectionObserver is efficient — it runs off the main thread and batches entries.

Gotchas

  • Do NOT lazy load the LCP image — it is above the fold and must load immediately
  • Always set width and height on lazy images to reserve space and prevent CLS
  • IntersectionObserver does not work in iframes with different origins without additional configuration
  • Disconnect the observer when the component unmounts to prevent memory leaks

Code Snippets

Lazy load with CSS transition on load

// Progressive reveal with blur-up placeholder
const observer = new IntersectionObserver((entries) => {
  entries.forEach(({ isIntersecting, target }) => {
    if (!isIntersecting) return;
    target.src = target.dataset.src;
    target.onload = () => target.classList.add('loaded');
    observer.unobserve(target);
  });
}, { rootMargin: '300px' });

Context

When implementing custom lazy loading with callbacks, analytics, or fine-grained threshold control

Revisions (0)

No revisions yet.