For ordinary images below the fold, use the browser’s native loading="lazy" attribute. Reach for JavaScript when you need a custom loading threshold, need to defer something other than a normal image, or need a fallback for a browser population your project must support. Keep images visible at initial load—especially the likely Largest Contentful Paint (LCP) image—eager: deferring them can delay the page’s most important visual content.
Choose the simplest approach that fits
| Approach | Best for | Control | Tradeoff |
|---|---|---|---|
Native loading="lazy" |
Ordinary offscreen <img> and responsive <picture> images |
The browser chooses when to fetch | Minimal code; the threshold is not configurable |
IntersectionObserver |
Custom preload distance, nonstandard targets, or a needed fallback | Set an observation root and rootMargin |
More code and failure cases to manage |
| Scroll and resize handlers | A compatibility fallback where an observer is unavailable | Fully custom | More event handling and geometry work |
For typical page content, start with native lazy loading. Browsers that do not recognize the attribute ignore it and load the image normally. Modern browsers broadly support it, and the hint continues to work when JavaScript is disabled. See web.dev’s browser-level image lazy-loading guidance and MDN’s lazy-loading overview.
As an Amazon Associate I earn from qualifying purchases.
Keep visible and LCP images eager
Do not lazy-load an image likely to appear in the initial viewport. In particular, keep the likely LCP image discoverable in the initial HTML with its real URL in src. A lazy image may not be fetched until the browser has enough layout information to determine that it is in or near view; that can add avoidable delay to the largest visible element. The web.dev LCP guidance says not to lazy-load the LCP image.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →The default loading behavior is eager, so a normal visible image does not need an explicit loading="eager". Use that value only if tooling or a particular implementation requires it. If the likely LCP image needs a priority hint, fetchpriority="high" may help signal its importance; use that signal sparingly and verify the browser’s actual resource priority rather than assuming it changed.
#1 Best Overall
Use native lazy loading for ordinary offscreen images
Add loading="lazy" to images that are below the fold, and provide their intrinsic dimensions so the browser can reserve space before the file arrives:
<img src="gallery-01.webp" loading="lazy" width="800" height="600" alt="Description">
For responsive images inside a <picture>, put loading on the fallback <img>:
<picture>
<source srcset="gallery-01.avif" type="image/avif">
<source srcset="gallery-01.webp" type="image/webp">
<img src="gallery-01.jpg" loading="lazy" width="800" height="600" alt="Description">
</picture>
Native loading leaves the fetch distance to browser heuristics; authors cannot set its threshold with the attribute. That is usually an advantage: no observer setup or fallback library is needed for regular images. Avoid adding a library solely out of habit. Feature detection with 'loading' in HTMLImageElement.prototype is available if you have a demonstrated need for a fallback.
Rank #2
Use IntersectionObserver when you need custom control
IntersectionObserver notifies your code when a target intersects a viewport or another observation root. A positive bottom rootMargin lets you start loading before an image reaches the viewport. For example, web.dev uses 0px 0px 256px 0px as an illustration—not a universal best setting. The right buffer depends on image placement, viewport size, connection conditions, and how quickly the image can be fetched.
Runnable JavaScript example
This example observes images marked with data-src. It transfers each URL into src when the image approaches the viewport, then stops observing that image. Keep critical images out of this deferred set, and ensure the markup provides dimensions.
<img
class="deferred-image"
data-src="gallery-01.webp"
width="800"
height="600"
alt="Description"
>
<script>
(() => {
const images = document.querySelectorAll("img.deferred-image[data-src]");
if (!("IntersectionObserver" in window)) {
images.forEach((img) => {
img.src = img.dataset.src;
img.removeAttribute("data-src");
});
return;
}
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute("data-src");
observer.unobserve(img);
});
}, {
root: null,
rootMargin: "0px 0px 256px 0px",
threshold: 0
});
images.forEach((img) => observer.observe(img));
})();
</script>
With JavaScript disabled, this example’s data-src URLs are not fetched. If those images are meaningful content, provide a non-JavaScript path or use native loading="lazy" instead. Do not make important content permanently inaccessible behind a placeholder. Keep actual image URLs in crawlable markup where possible; Google recommends that visible lazy-loaded content load without user interaction and that relevant image URLs appear in rendered HTML’s src attributes. See Google Search Central’s lazy-loaded content guidance.
Set the buffer from observation, not guesswork
A larger positive bottom margin starts more image requests earlier, which can make images ready sooner but also reduces the amount of work deferred. A smaller margin defers more work but can expose a late image on a slow connection. Test representative mobile and desktop viewports and adjust based on the waterfall and the point at which images become visible; no single threshold suits every site.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
When IntersectionObserver is unavailable
The example falls back to loading all deferred URLs immediately. That prioritizes content availability over deferral in an unsupported browser. If that tradeoff is unsuitable for the browsers your project supports, MDN describes polyfilling IntersectionObserver or using scroll, resize, and orientation-change handlers as alternatives. Avoid repeatedly measuring every image’s geometry on each scroll event when a browser observer is available.
Prevent layout shifts and accidental early loads
Give images width and height, or reserve equivalent space with a dimensionally consistent placeholder or aspect ratio. Without dimensions, an unloaded image can initially occupy no space, causing content to jump when it loads. In a gallery, zero-size images may also lead the browser to treat many or all images as initially fitting in the viewport, undermining lazy loading. Read web.dev’s lazy-loading best practices for the relationship between reserved space and deferred images.
Rank #4
When JavaScript inserts large images, decoding can require main-thread work. HTMLImageElement.decode() is one possible technique for coordinating image decoding, but it adds complexity and is not required for ordinary small images. Use it only when measurement shows that decode timing is a real issue.
Verify the performance and crawlability
- Check initial visibility at multiple viewport sizes. The same image may be below the fold on desktop and visible on mobile. Keep visible candidates, particularly the likely LCP image, out of the deferred set.
- Inspect the network waterfall. Confirm that noncritical images are fetched later than critical resources, and check the resource priority assigned to the likely LCP image.
- Test the experience, not just the request count. Scroll at representative speeds and network conditions. Visible images should appear promptly; deferral should not leave blank gaps as users reach them.
- Inspect rendered HTML for search engines. Google Search Central’s URL Inspection Tool can show rendered HTML. Confirm relevant image URLs are present in
src, and verify that content loads when it becomes visible without requiring a click or other interaction. - Compare performance measurements. Browser heuristics, viewport dimensions, network conditions, and placement all affect the result. Assess lab results and field data where available instead of assuming that adding lazy loading improves every page.
Common problems and fixes
- The main image or LCP is late: remove
loading="lazy"or JavaScript deferral from that image, keep its URL in the initial HTML, and inspect its priority. Considerfetchpriority="high"only for a likely LCP candidate. - Images pop into place and shift the page: add intrinsic dimensions or reserve the correct aspect ratio before loading.
- All gallery images download immediately: check that unloaded images have reserved dimensions and that your JavaScript is observing only the intended offscreen targets.
- Images never load with JavaScript disabled: do not rely on
data-srcalone for meaningful content. Use native lazy loading or provide an ordinary loading fallback. - Google’s rendered page has no image URL in
src: ensure the final URL is present in rendered markup and that visibility, not a user interaction, triggers loading. - An image appears late despite a positive margin: increase the preload buffer only after checking the request waterfall and real scroll behavior. A margin is a trigger distance, not a guarantee of fetch completion by the time the image enters view.
- Custom lazy loading adds work without a measured gain: simplify to native
loading="lazy"for ordinary images, or remove deferral for content that is already critical.
Or skip the browser setup
If your task is to capture a page rather than implement lazy loading on your own site, ScreenshotNeo provides a one-request screenshot API. Its browser accepts cookie or consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; those steps can each be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify the page verdict and billing status in headers. It also offers an MCP server with screenshot, page-info, and PDF tools for AI agents.
The following cURL request saves a screenshot as WebP. Create an API key first, replace the placeholder, and adapt the target URL as needed. See the ScreenshotNeo API documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
ScreenshotNeo’s free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Learn about ScreenshotNeo or sign up for 1,000 free screenshots a month with no card.
Best Value
Frequently Asked Questions
Does native lazy loading require JavaScript?
No. The browser’s loading="lazy" hint is independent of JavaScript and continues to work when JavaScript is disabled.
Can I set the native lazy-loading distance?
No. The browser chooses when to fetch an image marked loading="lazy"; use an observer if you need an author-controlled trigger margin.
Free tools Windows power users keep installed
One-click scans. No signup required.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




