Images are the heaviest assets on most web pages. A typical blog post might have 5 to 10 images, each weighing 200KB to 2MB. If the browser downloads all of them on load, visitors wait for megabytes of data they may never scroll down to see.
Lazy loading defers image downloads until the image is about to enter the viewport. The browser only fetches images the user is going to see. For a page with 10 images where only 3 are visible without scrolling, lazy loading reduces the initial page weight by around 70%.
The result is faster initial load times, lower bandwidth usage, and better Core Web Vitals scores. And the basic version is a single HTML attribute.
Before you lazy load, make sure your images are sized and compressed. An uncompressed 4MB image is still 4MB even with lazy loading. Run them through the Image Compressor to cut file size without visible quality loss.
Native Lazy Loading with the loading Attribute
The simplest implementation is the native loading attribute:
`html
`
That is it. The browser handles the rest. It monitors the scroll position and fetches the image when it gets close to the viewport (typically 1250 to 2500 pixels before the image scrolls into view, depending on connection speed).
Important details:
Always include width and height: without dimensions, the browser cannot reserve space for the image before it loads. This causes layout shifts (elements jumping around as images load), which hurts your Cumulative Layout Shift (CLS) score.
Do not lazy load above-the-fold images: images visible without scrolling should load immediately. Lazy loading them delays their appearance and hurts Largest Contentful Paint (LCP). Use loading="eager" (the default) for hero images, logos, and first-visible content.
Browser support: the loading attribute is supported in all major browsers since 2020. No polyfill needed in 2026.
Iframes too: loading="lazy" works on iframes as well. Embedded videos, maps, and third-party widgets can all be lazy loaded.
The Image Resizer prepares images at the exact dimensions you need, so the browser does not download a 4000px image just to display it at 800px.

Intersection Observer API for Advanced Control
When native lazy loading does not give you enough control, the Intersection Observer API lets you define exactly when and how images load:
`javascript
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
}, {
rootMargin: '200px 0px'
});
document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});
`
The HTML uses data-src instead of src, with a tiny placeholder or blank pixel as the initial source:
`html
`
Advantages of Intersection Observer over native lazy loading:
Custom threshold: control exactly how far from the viewport the image starts loading. The rootMargin option lets you set a buffer zone.
Loading animations: trigger fade-in animations when images load, creating a polished user experience.
Priority ordering: load images in a specific order rather than whatever the scroll position dictates.
Conditional loading: load different image sizes based on viewport width or connection speed.
The trade-off is more code to maintain. For most projects, native loading="lazy" is sufficient.
When native lazy loading does not give you enough control, the Intersection Observer API lets you define exactly when and how images load: ```javascript const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; observer.unobserve(img); } }); }, { rootMargin: '200px 0px' }); document.querySelectorAll('img[data-src]').forEach(img => { observer.observe(img); }); ``` The HTML uses `data-src` instead of `src`, with a tiny placeholder or blank pixel as the initial source: ```html <img data-src="photo.jpg" src="placeholder.svg" alt="Description"> ``` Advantages of Intersection Observer over native lazy loading: **Custom threshold**: control exactly how far from the viewport the image starts loading.
Responsive Images and Lazy Loading Together
Modern websites serve different image sizes for different screen widths using srcset and sizes. Combining this with lazy loading gives you the best of both worlds:
`html
`
The browser selects the appropriate image size from srcset based on the viewport width AND only downloads it when the image approaches the viewport thanks to loading="lazy". A mobile user never downloads the 1200px version, and neither the mobile nor the desktop user downloads anything until they scroll near the image.
For the element with different image formats:
`html
`
The loading attribute on the inside applies to whichever source the browser selects. AVIF and WebP formats are 25 to 50% smaller than JPEG, and combined with lazy loading, the bandwidth savings multiply.
Impact on Core Web Vitals
Lazy loading directly affects two of the three Core Web Vitals:
Largest Contentful Paint (LCP): lazy loading below-the-fold images reduces the work the browser does during initial load, which can improve LCP. But lazy loading the LCP element itself (usually a hero image) hurts LCP because it introduces a delay. Identify your LCP element using Chrome DevTools or PageSpeed Insights and make sure it loads eagerly.
Cumulative Layout Shift (CLS): lazy loaded images without explicit width and height attributes cause layout shifts when they load. The image placeholder takes zero space, and when the image appears, everything below it jumps down. Always set width and height or use CSS aspect-ratio to reserve space.
Interaction to Next Paint (INP): lazy loading reduces the amount of DOM processing during initial load, which can indirectly improve INP by reducing main thread work.
Measure the impact after implementing lazy loading. Use Lighthouse, PageSpeed Insights, or Web Vitals extension to compare before and after scores.
Minify your HTML after implementing lazy loading to keep page weight down. The HTML Minifier removes whitespace and comments from your markup without changing behaviour.

Common Lazy Loading Mistakes
Lazy loading everything: do not apply loading="lazy" to every image on the page. Above-the-fold images (typically the first 1 to 3 images) should load immediately. Lazy loading them delays the initial visual experience and hurts performance metrics.
Missing dimensions: the most common CLS issue with lazy loading. Without width and height, the browser allocates zero space for the image. When it loads, the page reflows. Always specify dimensions.
Placeholder images that are too large: using a 50KB blurred placeholder defeats the purpose of lazy loading. Use tiny SVG placeholders (under 1KB), solid color backgrounds, or CSS aspect-ratio boxes.
Not compressing lazy loaded images: lazy loading reduces when images load, not how large they are. A 3MB image is still 3MB when it finally loads. Compress all images regardless of loading strategy.
JavaScript-only lazy loading without fallback: if you use Intersection Observer and JavaScript fails to load (network error, script blocked), no images will ever load. Always provide a
Infinite scroll without limits: lazy loading content in an infinite scroll without any pagination fallback creates accessibility issues, makes specific content unfindable, and uses unbounded memory. Add pagination as an alternative navigation method.
FAQ
Does lazy loading affect SEO?
No, as long as you use native loading="lazy" or properly implemented Intersection Observer with
Should I lazy load background images set via CSS?
CSS background images are not affected by the HTML loading attribute. To lazy load CSS backgrounds, use Intersection Observer to add a CSS class that sets the background-image only when the element enters the viewport. This works well for hero sections that are initially below the fold.
How far from the viewport should lazy loading trigger?
The browser handles this automatically with native lazy loading, typically starting the download 1250 to 2500 pixels before the image enters the viewport. With Intersection Observer, a rootMargin of 200 to 500 pixels works well for most connections. Too small and users see loading placeholders; too large and you lose the bandwidth savings.
Does lazy loading work on mobile?
Yes. All modern mobile browsers support native lazy loading. Mobile is where lazy loading provides the biggest benefit because mobile connections are often slower and data is more expensive. The bandwidth savings on mobile can sharpen the user experience by several seconds on real-world connections.
### Does lazy loading affect SEO.
Markdown Table Generator: Build Clean Tables Without the Pain
Markdown tables are simple until the pipes and dashes stop lining up. Learn the syntax, alignment tricks, and a free tool that formats tables for you.
CSV to JSON: Convert Spreadsheet Data for APIs and Code
Turn a CSV export into clean JSON for APIs, imports, and scripts. Learn how the conversion works, common pitfalls with types and quotes, and a free tool.
JSON Guide: Format, Validate, and Convert JSON Files
JSON guide for developers: syntax rules, common parse errors, formatting and schema validation, plus how to convert between JSON and CSV files.
