What Are Offscreen Images? Lazy Loading Complete Guide (2026)

Saar Twito8 min read
Saar Twito
Saar TwitoFounder & SEO Engineer

Hi, I'm Saar - a software engineer, SEO specialist, and lecturer who loves building tools and teaching tech.

View author profile →

What Are Offscreen Images?

Offscreen images are images rendered outside the user's initial viewport that the browser downloads immediately on page load — even though the user can't see them yet. Performance audits flag pages where deferring offscreen images via lazy loading would save more than ~50 KB. The fix is almost always a one-line attribute change: loading="lazy".

Key Facts (TL;DR)

  • Audit threshold: > 50 KB of deferrable image bytes triggers the "Defer offscreen images" audit.
  • Typical waste: On long marketing pages and product listings, 60–80% of images are offscreen at first paint — pure bandwidth waste during the critical load window.
  • The fix: Native browser-level lazy loading via <img loading="lazy"> — supported in 95%+ of browsers since 2020.
  • Critical exception: Never lazy-load above-the-fold images — especially the LCP element. Use fetchpriority="high" on the hero instead.
  • CLS connection: Lazy-loaded images still need explicit width and height — otherwise they cause layout shifts when they finally arrive.
  • Page-weight context: Images are 40–55% of total page weight on average (HTTP Archive). Deferring offscreen images is often the single largest weight win on long pages.

Think of offscreen image loading like a restaurant pre-cooking every dish on the menu the moment you sit down — even the desserts you might not order. It wastes the kitchen's capacity right when the appetizers (your hero, your headline, your above-the-fold content) need it most.

Why Offscreen Images Hurt Performance

An offscreen image isn't free just because the user can't see it. It costs bandwidth, network connections, decoding time, and — most damagingly — contention with the LCP image during the critical load window.

  • LCP delay:The browser has limited parallel connections (typically 6 per origin). When 30 offscreen images are queued at the same priority as your hero, the hero's bytes arrive late — directly inflating LCP.
  • Wasted bandwidth:If a visitor never scrolls past the first viewport, every offscreen image was a free gift to the bit bucket. On long pages, that's often 70%+ of the page's total image weight.
  • Higher mobile data costs: On metered mobile plans, downloading invisible images is a real cost passed to the user. It feels like the site disrespects their data.
  • Battery drain: Network radio activity is one of the largest battery drains on mobile. More image bytes = more radio time = faster battery depletion.
  • Total byte weight:The audit's 50 KB threshold is usually an underestimate of real-world impact — many pages have 500 KB to several MB of deferrable image bytes.

How Lazy Loading Works (and the One Line That Fixes It)

Native lazy loading shipped in Chrome 76 and is now in 95%+ of browsers. It's a single HTML attribute. The browser uses its own viewport-proximity heuristics to defer the fetch until the image is close to scrolling into view.

<!-- BEFORE: every image fetched immediately, even offscreen ones -->
<img src="below-the-fold.jpg" alt="Product detail" />

<!-- AFTER: browser defers the fetch until the image nears the viewport -->
<img
  src="below-the-fold.jpg"
  alt="Product detail"
  width="800"
  height="600"
  loading="lazy"
/>

Critical: Don't Lazy-Load the LCP Image

The hero image and any above-the-fold imagery should NOT use loading="lazy". Doing so triggers an unnecessary IntersectionObserver-style delay before the most important image starts downloading — directly inflating LCP. Multiple field studies have shown this single mistake can add 500 ms to 1 s to LCP.

Use fetchpriority="high" on the LCP image instead — it tells the browser to upgrade that fetch to the front of the queue.

<!-- LCP / hero image: upgrade priority, do NOT lazy-load -->
<img
  src="hero.jpg"
  alt="Product hero"
  width="1600"
  height="900"
  loading="eager"
  fetchpriority="high"
/>

<!-- Everything below the fold: defer it -->
<img
  src="feature-1.jpg"
  alt="Feature 1"
  width="600"
  height="400"
  loading="lazy"
/>

How to Identify Offscreen Image Issues

Offscreen image waste is reported as a list of specific URLs and the bytes you'd save by deferring them. Pull the list, then mechanically apply loading="lazy"to everything that isn't above the fold.

  • Greadme's deep scan — surfaces every offscreen image flagged by the audit, ranked by potential KB savings, and pairs each with an AI-generated fix or one-click GitHub PR.
  • Greadme's crawler scan — runs the audit across every indexable page so you can see which templates (product, listing, article) ship the most deferrable bytes.
  • Greadme's AI visibility analyzer — checks whether image-driven LCP is dragging your page out of the band that AI answer engines prefer to cite.
  • web.dev performance audits — the "Defer offscreen images" opportunity lists every offending URL and its KB cost.
  • Chrome DevTools → Performance panel — record a load with the viewport at the top of the page; any image that downloads but never paints in the first 2 seconds is a lazy-load candidate.

8 Proven Lazy-Loading Patterns

1. Add loading="lazy" to Every Below-the-Fold Image

The single highest-leverage fix. Walk every template and add the attribute to every <img>that isn't in the first viewport.

<img
  src="article-image.jpg"
  alt="Diagram of the system"
  width="1200"
  height="675"
  loading="lazy"
/>

2. Use fetchpriority="high" on the LCP Image

Identify the LCP element (usually the hero image) and add fetchpriority="high" with loading="eager". This is the opposite of lazy loading — you're telling the browser this image is the most important byte on the page.

3. Lazy-Load iframes Too

Embedded videos, maps, and social widgets are often the heaviest offscreen content on a page. <iframe loading="lazy"> works the same way as on images.

<iframe
  src="https://www.youtube.com/embed/VIDEO_ID"
  loading="lazy"
  width="560"
  height="315"
  title="Product walkthrough"
></iframe>

4. Always Set width and height (or aspect-ratio)

Lazy-loaded images still cause layout shift if their dimensions aren't reserved — the layout collapses to zero height until the image arrives. Either set explicit width and height attributes, or use CSS aspect-ratio.

/* Reserve space proportionally for fluid images */
img {
  aspect-ratio: 16 / 9;
  width: 100%;
  height: auto;
}

5. Use IntersectionObserver for Custom Behavior

When you need fade-in effects, blur-up placeholders, or fine-grained control over when the fetch fires, fall back to IntersectionObserver. The pattern: store the URL in data-src, swap it to src when the element intersects.

const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (!entry.isIntersecting) continue;
    const img = entry.target;
    img.src = img.dataset.src;
    img.classList.add('loaded');
    observer.unobserve(img);
  }
}, { rootMargin: '200px' });  // start 200px before visible

document.querySelectorAll('img[data-src]').forEach((img) => {
  observer.observe(img);
});

The rootMargin: "200px"means "start fetching when the image is within 200 px of the viewport" — usually enough lead time to hide the load behind the scroll.

6. Use Low-Quality Image Placeholders (LQIP)

For visually-rich pages, render a tiny (1–3 KB) blurred version inline as a placeholder, then swap to the full image when it intersects. This avoids the "blank space pops in" effect on slow connections.

Most framework image components (Next.js <Image> with placeholder="blur", Astro, Gatsby) implement LQIP automatically.

7. Lazy-Load Carousel and Tab Content

Image carousels and tabbed interfaces commonly load every slide's image upfront, even though only one is visible. Apply loading="lazy"to all but the first slide's image.

8. Lazy-Load CSS Background Images via IntersectionObserver

The loading attribute only works on <img> and <iframe>. For CSS background-image, use IntersectionObserver to add a class that swaps in the background only when the element nears the viewport.

<div class="hero-bg" data-bg="/hero.jpg"></div>

<script>
const bgObserver = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (!entry.isIntersecting) continue;
    const el = entry.target;
    el.style.backgroundImage = `url(${el.dataset.bg})`;
    bgObserver.unobserve(el);
  }
}, { rootMargin: '200px' });

document.querySelectorAll('[data-bg]').forEach((el) => bgObserver.observe(el));
</script>

Common Lazy-Loading Pitfalls and Fixes

Problem: LCP Got Worse After Adding loading="lazy"

What's happening: The hero image was lazy-loaded along with everything else. loading="lazy" introduces a small delay before the browser decides to fetch — disastrous on the LCP element.

Fix: Remove loading="lazy" from any image that's above the fold or is the LCP element. Replace with loading="eager" and fetchpriority="high".

Problem: Lazy-Loaded Images Cause Layout Shifts (Bad CLS)

What's happening: The images don't have explicit width and height, so the layout reserves zero space until each image arrives — then jumps.

Fix: Always include width and height attributes (or aspect-ratio CSS) on every lazy-loaded image. The browser uses these to compute the aspect ratio and reserve space before the fetch completes.

Problem: Images Pop In Visibly During Scroll

What's happening: The browser's default lazy-load threshold fired too late — the image started downloading after it had already scrolled into view.

Fix: If using IntersectionObserver, set rootMargin: "200px" or larger so the fetch starts before the image enters the visible area. If using native lazy loading, the browser usually handles this well — but pair with a low-quality placeholder for a smoother visual.

Problem: Search Engines Aren't Indexing Lazy-Loaded Images

What's happening: A custom JavaScript lazy-loader replaced src with data-src, leaving the <img> with no real source until JS runs. Some crawlers don't execute the JS.

Fix: Prefer native loading="lazy" — it keeps a real src in the HTML and is fully understood by Google, Bing, and AI crawlers. If you must use a JS-driven approach, include a <noscript> fallback with the standard <img src>.

Lazy Loading vs Eager Loading vs fetchpriority

AttributeWhat It DoesUse ForEffect on LCP
loading="eager" (default)Browser fetches the image as part of normal page loadAbove-the-fold images, especially the LCP elementNeutral — this is the baseline behavior
loading="lazy"Browser defers fetch until image nears viewportBelow-the-fold images, iframes, embedsImproves LCP (frees connections for the hero)
fetchpriority="high"Upgrades the fetch priority for this resourceThe LCP image specifically — pair with loading="eager"Improves LCP by 200–500 ms on most pages
fetchpriority="low"Downgrades fetch priorityDecorative images that aren't lazy-loadableIndirect improvement (less contention)
<link rel="preload">Forces an early fetch from the document headThe LCP image when it's referenced from CSS backgroundImproves LCP, but only when used surgically

FAQ

What is a good threshold for lazy-loading images?

The audit fires when more than ~50 KBof deferrable image bytes are loaded immediately. In practice, you should lazy-load every image that isn't in the initial viewport on the most common breakpoint — typically the second screen down and beyond.

Is loading="lazy" safe to use everywhere?

Almost — it's safe on every image below the fold, on iframes, and on embeds. It is notsafe on the LCP image or on any image that's already visible at first paint. Lazy-loading the hero is one of the most common LCP regressions in performance work.

Does loading="lazy" work without JavaScript?

Yes. Native lazy loading is a browser feature, not a JS feature. It works in 95%+ of browsers since 2020 with no script tag, no library, and no observer code. That's the main reason it should be your default — IntersectionObserver-based libraries are only worth the complexity for custom effects.

Will lazy loading hurt my image SEO?

Not when implemented correctly. Google explicitly supports native loading="lazy" and indexes the images normally. The risk only appears with custom JS implementations that strip srcand rely on JS to populate it later — and even then, Google will usually crawl the rendered HTML. Stick to native lazy loading and you're safe.

How do I lazy-load images in a single-page app or React component?

Use the framework's image component if available (Next.js <Image>, Nuxt <NuxtImg>, Astro <Image>) — they emit loading="lazy" by default and handle fetchpriority, responsive sizing, and modern formats in one go. Otherwise, render a plain <img loading="lazy"> with explicit width and height.

Does lazy loading affect AI search engines like ChatGPT and Perplexity?

Indirectly, yes. AI answer engines preferentially cite pages that already rank well in traditional search, and offscreen-image waste hurts LCP, which feeds Core Web Vitals, which feeds rankings. Pages that defer offscreen images correctly get faster LCP, better rankings, and better odds of being cited in Google AI Overviews and Perplexity answers.

Should I lazy-load images that are just below the fold?

Yes — the lazy-loading penalty is small (typically a few hundred milliseconds of fetch delay), and the upside is removing the image from the critical-path connection pool. The browser's native heuristic is generous enough that "just below the fold" images are usually fetched quickly enough to feel instant on scroll.

Conclusion

Offscreen image waste is one of the simplest performance bugs to fix and one of the most expensive to leave alone. A single attribute — loading="lazy"— handed to every below-the-fold image clears the audit on most sites and frees up connections for the hero, directly improving LCP. The discipline is: lazy-load everything that isn't visible, eager-load and prioritize the one image that is.

Always pair lazy loading with explicit width and height so the layout doesn't shift when images arrive — otherwise you've traded an offscreen-image penalty for a CLS penalty. Run a Greadme deep scan to get the exact list of pages where deferring offscreen images would pay back the most KB, and fix them in priority order.