Images often dominate page weight, especial on content-rich sites like e-commerce platforms. Loading all images upfront can severely delay perceived performance and increase bounce rates. Lazy loading defers image fetching until they’re likely to become visible—significantly improving initial load time and resource efficiency.
The core mechanism relies on the fact that browsers only initiate image requests when the src attribute is present. By initially omitting src and storing the intended URL in a custom attribute (e.g., data-src), we gain full control over when loading occurs.
To detect visibility, modern implementations favor getBoundingClientRect() over manual offset calculations. This method returns an element’s position relative to the viewport, eliminating dependency on scroll position arithmetic and handling complex layouts—including transforms and iframes—more reliably.
.lazy-image {
display: block;
width: 100%;
height: 300px;
margin-bottom: 20px;
background-color: #f5f5f5;
}
<img class="lazy-image" data-src="./assets/photo-1.jpg" alt="Landscape">
<img class="lazy-image" data-src="./assets/photo-2.jpg" alt="Portrait">
<img class="lazy-image" data-src="./assets/photo-3.jpg" alt="Still life">
function isInViewport(element) {
const rect = element.getBoundingClientRect();
return rect.top <= window.innerHeight && rect.bottom >= 0;
}
function initLazyLoading() {
const candidates = document.querySelectorAll('img[data-src]');
const loadIfVisible = () => {
candidates.forEach(img => {
if (isInViewport(img)) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
}
});
};
// Throttle scroll handler to avoid excessive calls
let isThrottled = false;
window.addEventListener('scroll', () => {
if (!isThrottled) {
requestAnimationFrame(() => {
loadIfVisible();
isThrottled = false;
});
isThrottled = true;
}
});
// Trigger initial check for elements already in view
loadIfVisible();
}
// Start lazy loading after DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initLazyLoading);
} else {
initLazyLoading();
}
For production-grade applications, consider standardized solutions:
IntersectionObserverAPI: Replaces scroll-based detection with efficient, passive observation. It natively handles throttling, supports root margins, and works inside scrollable containers.lazysizes: A lightwieght, highly optimized library supporting responsive images (srcset,sizes), progressive enhancement via<noscript>, and plugins for blur-up placeholders and animations.- Framework-specific tools: Vue apps can use
vue-lazyload, React projects benefit fromreact-lazy-load-image-component, and Svelte developers may adoptsvelte-inviewor similar.
When using third-party libraries, always verify compatibility with your build toolchain and ensure fallback behavior (e.g., inline placeholder or <noscript> content) remains functional without JavaScript.