Lazy Loading Images Guide
Calculate bandwidth savings, generate framework-specific code, and see lazy loading in action. All tools run client-side — no data leaves your browser.
Bandwidth Savings Calculator
Code Snippet Generator
Intersection Observer Configuration
Live Lazy Loading Demo
Scroll down inside the container below. Images load as they enter the viewport, just like real lazy loading. The counter tracks loaded vs. total images.
What Is Lazy Loading?
Lazy loading is a strategy that defers the loading of non-critical resources until they are actually needed. For images, this means only downloading images that are currently visible in the user's viewport (or about to become visible), rather than downloading every image on the page at once. The technique dramatically reduces initial page weight, speeds up Largest Contentful Paint (LCP), and saves bandwidth for users who never scroll to the bottom of the page.
Consider a blog post with 20 images, each averaging 80KB. Without lazy loading, the browser downloads all 20 images (1,600KB) immediately on page load, even though the user can only see 2-3 images in their viewport. With lazy loading, only those 2-3 visible images load initially (160-240KB), and the remaining images load progressively as the user scrolls. The initial page weight drops by 85%, and the page becomes interactive in a fraction of the time.
Lazy loading became a first-class browser feature in 2019 when Chrome introduced the loading="lazy" attribute. By 2026, native lazy loading is supported in all major browsers (Chrome, Firefox, Safari, Edge) with over 95% global coverage. This means you can implement lazy loading with a single HTML attribute — no JavaScript required for the basic case.
Native Lazy Loading with loading="lazy"
The simplest implementation is the native HTML attribute. Add loading="lazy" to any <img> tag that is below the fold:
<img src="photo.webp" loading="lazy" width="800" height="600" alt="Description">
The browser handles everything: it detects when the image is approaching the viewport and starts the download. The exact trigger distance varies by browser (Chrome uses a distance threshold that adapts to connection speed), but you don't need to configure anything. Three critical rules when using native lazy loading:
- Never lazy load above-the-fold images. Your hero image and LCP element should use
loading="eager"(the default) orfetchpriority="high". Lazy loading the LCP element is one of the most common performance anti-patterns. - Always include width and height attributes. Without explicit dimensions, the browser cannot reserve space for the image before it loads, causing Cumulative Layout Shift (CLS). The attributes don't need to match the display size — the browser uses them to calculate the aspect ratio.
- Provide meaningful alt text. Lazy loading changes when an image loads, not whether it loads. Screen readers and search engines still need descriptive alt text.
Intersection Observer API
For more control over lazy loading behavior, the Intersection Observer API lets you programmatically detect when elements enter or leave the viewport. Unlike scroll event listeners (which fire continuously and can cause jank), Intersection Observer is asynchronous and highly performant — the browser handles the intersection calculation off the main thread.
The key configuration options are rootMargin and threshold. The rootMargin extends the detection area beyond the viewport — a value of "200px 0px" starts loading images 200 pixels before they scroll into view, giving them a head start. The threshold determines what fraction of the element must be visible to trigger the callback — 0 triggers when even a single pixel enters the margin, while 1.0 requires the entire element to be visible. For lazy loading images, a rootMargin of 200px and threshold of 0 is the recommended starting point.
Use the Intersection Observer configuration builder above to experiment with different rootMargin and threshold values. The generated code is copy-paste ready for any project. For most use cases, the native loading="lazy" attribute is sufficient, but Intersection Observer is preferred when you need custom animations, blur-up effects, or precise control over loading behavior.
Above-the-Fold Considerations
The most critical decision in lazy loading is determining which images are "above the fold" — visible in the initial viewport without scrolling. These images must load immediately (eagerly) because they directly affect LCP. The problem is that "the fold" varies by device: a desktop at 1440x900 shows different content than a mobile at 375x812.
A practical approach: mark the first 1-3 images as eager and everything else as lazy. For product pages, the hero product image is always eager. For blog posts, the featured image is eager. For category grids, the first row of thumbnails (typically 3-4) should be eager. When in doubt, use Chrome DevTools' Performance panel to identify which image is the LCP element, and make sure that specific image loads eagerly with fetchpriority="high".
LQIP and Blur-Up Techniques
Low Quality Image Placeholder (LQIP) provides a smooth visual experience during lazy loading. Instead of showing nothing (or a blank space) while an image loads, you show a tiny, blurred preview that transitions to the full image. The technique works in three steps:
- Generate a tiny thumbnail (typically 20x15 pixels, under 1KB as base64) at build time.
- Inline the thumbnail as a
data:URI in thesrcattribute, with a CSSfilter: blur(20px)andtransform: scale(1.1)to hide the pixelation. - When the full image loads, crossfade from the blurred placeholder to the sharp image using a CSS transition on opacity.
The total overhead per image is approximately 200-500 bytes for the base64 placeholder, which is negligible compared to the full image. The visual result is significantly better than a blank space or a loading spinner. Next.js's <Image> component and Gatsby's gatsby-plugin-image implement LQIP automatically when you use their image optimization pipelines.
Fallbacks and Progressive Enhancement
Native loading="lazy" is progressive by nature — browsers that don't support it simply load the image eagerly. No JavaScript polyfill is needed; the experience degrades gracefully to the traditional behavior. For Intersection Observer-based implementations, include a fallback that loads all images immediately if the API is unavailable:
if ('IntersectionObserver' in window) {
// Set up lazy loading with observer
} else {
// Load all images immediately
document.querySelectorAll('img[data-src]').forEach(img => {
img.src = img.dataset.src;
});
}
In practice, Intersection Observer support in 2026 is nearly universal (97%+), so this fallback rarely executes. But defensive coding ensures no images are permanently invisible on legacy browsers or unusual environments (embedded webviews, older smart TV browsers).
Performance Impact in Numbers
The impact of lazy loading depends on how many images are below the fold and how large they are. Based on HTTP Archive data and real-world measurements:
- Blog posts: 70-85% reduction in initial image payload (20 images, 3 above fold).
- E-commerce category: 75-90% reduction (40 products, 8 visible).
- Image galleries: 90-95% reduction (100+ images, 6-9 visible).
- LCP improvement: 0.5-2.0 seconds on 3G when combined with eager loading of the hero image.
- Bandwidth savings: For a site with 100,000 monthly pageviews and 1MB of lazy-loadable images per page, the monthly bandwidth savings is approximately 75-85 TB.
The performance benefit compounds with other optimizations. Lazy loading + WebP conversion + responsive srcset can reduce total image transfer by 95% compared to unoptimized eager loading of full-resolution JPEGs. Use the Krzen image compressor to optimize the actual image files, and lazy loading to control when they download.
Frequently Asked Questions
Does loading="lazy" work in all browsers?
Native lazy loading with the loading="lazy" attribute is supported in Chrome 77+, Firefox 75+, Edge 79+, Safari 15.4+, and Opera 64+. As of 2026, global browser support exceeds 95%. For browsers that don't support it, images load normally (eagerly), so there is no broken experience.
Should I lazy load above-the-fold images?
No. Never lazy load images that are visible in the initial viewport. Lazy loading above-the-fold images delays LCP because the browser waits before starting the download. Always use loading="eager" (the default) or add fetchpriority="high" for your hero image and LCP element.
How much bandwidth does lazy loading save?
Savings depend on page layout. A typical blog post with 20 images where only 3 are above the fold saves roughly 85% of image bandwidth on initial load. An e-commerce category page with 40 products showing 8 in the viewport saves about 80%. Use the calculator above to estimate savings for your layout.
What is LQIP and blur-up technique?
LQIP (Low Quality Image Placeholder) shows a tiny, blurred preview (under 1KB as base64) while the full image loads. The blur-up variant applies a CSS blur filter, then transitions to the sharp full image when loaded. This eliminates layout shift and provides visual feedback during loading.
What rootMargin should I use for Intersection Observer?
A rootMargin of "200px 0px" is a good default. This starts loading images 200px before they enter the viewport. For fast-scrolling pages (infinite feeds), increase to "400px 0px". For bandwidth-constrained mobile scenarios, use "100px 0px". A threshold of 0 triggers as soon as any pixel enters the margin area.