Hardware FixRecommendedDevice not working? Your driver may be the problemCheck updates for common hardware issues.Fix DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run Scan×
Skip to content
World desk7 min

How to Lazy Load Images with JavaScript Without Hurting Performance

Use native lazy loading for ordinary offscreen images and IntersectionObserver only when custom control is needed. Keep LCP images eager, reserve image dimensions, and verify the result in the network waterfall and rendered HTML.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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. Consider fetchpriority="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-src alone 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.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.