Image Lazy Loading Guide
Method 1: Native loading="lazy" (Recommended)
Browser support: 92%+. No JavaScript needed.
<!-- Always set width and height to prevent layout shift --> <img src="photo.webp" loading="lazy" width="800" height="600" alt="Description of image" > <!-- Do NOT lazy-load the LCP image (above the fold) --> <img src="hero.webp" loading="eager" fetchpriority="high" ...>
Method 2: Intersection Observer API
More control — custom thresholds, animations, fallbacks.
// HTML: use data-src instead of src
// <img class="lazy" data-src="photo.webp" alt="...">
const lazyImages = document.querySelectorAll('img.lazy');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
observer.unobserve(img);
}
});
}, {
rootMargin: '200px 0px', // load 200px before visible
threshold: 0
});
lazyImages.forEach(img => observer.observe(img));
Blur-Up / LQIP Technique
/* CSS: show a tiny blurred placeholder */
.lazy-wrapper {
position: relative;
overflow: hidden;
}
.lazy-placeholder {
filter: blur(20px);
transform: scale(1.05); /* hide blur edges */
transition: opacity 0.4s;
}
.lazy-loaded .lazy-placeholder {
opacity: 0;
}
Quick Reference
| Approach | Browser Support | JS Required | Custom Offset |
|---|---|---|---|
| loading="lazy" | 92%+ | No | No |
| Intersection Observer | 97%+ | Yes | Yes |
| Library (lazysizes) | All | Yes (5KB) | Yes |