HTML Lazy Loading

Lazy loading is a technique that tells the browser to delay fetching images and iframes that are not immediately visible on screen, instead of downloading every resource the moment the page loads. HTML gives you this behavior for free through the loading attribute, with no JavaScript required. This matters because pages with dozens of images can waste enormous amounts of bandwidth and time downloading content a visitor may never scroll down to see, slowing down the parts of the page that actually matter first.

Overview / How it works

When the browser parses HTML and builds the DOM, it discovers <img> and <iframe> elements and normally begins fetching their resources (the src URL) as soon as it encounters them, regardless of where they will end up on the rendered page. On a long page, this means an image near the bottom competes for network bandwidth with the logo and hero image at the top, even though the user has not scrolled anywhere near it yet.

The loading attribute changes this. When set to lazy, the browser defers loading of that specific image or iframe until it gets close to the visible viewport (the part of the page currently on screen). The browser calculates a distance threshold — often a few hundred pixels or a fraction of the viewport height, and the exact value is left to each browser’s implementation — so that the content is fetched slightly before the user actually scrolls to it, avoiding a visible blank gap.

This is a rendering and networking optimization, not a change to the DOM structure or semantics. The element is still present in the DOM immediately; only the network request for its resource is postponed. Because it’s declared directly in markup and interpreted by the browser’s loading engine, no JavaScript, Intersection Observer polyfill, or third-party library is required for modern browsers.

It’s important to understand what lazy loading is not: it does not lazy-load background images set via CSS, it does not apply to elements without a src (like inline SVG), and it does not defer parsing or rendering of the surrounding HTML — only the fetching of the referenced resource.

Syntax

<img src="photo.jpg" alt="Description" loading="lazy" width="800" height="600">
<iframe src="https://example.com/embed" loading="lazy" width="560" height="315"></iframe>
Value Meaning
lazy Defer loading the resource until it nears the viewport.
eager Load the resource immediately, as soon as the browser parses the element (this is the default behavior if the attribute is omitted).
auto Let the browser decide the loading strategy itself (supported inconsistently; behaves like the browser’s default heuristic).
  • Applies to: <img> and <iframe> elements only.
  • Not applicable to: CSS background-image, <video> (which has its own preload attribute), or <svg>.
  • Works alongside: the src (or srcset) attribute — loading only changes timing, it never replaces the resource URL attributes.

Examples

Example 1: A basic lazy-loaded image

<article>
  <h2>Mountain Trip Report</h2>
  <p>We hiked for six hours before reaching the summit.</p>
  <img src="summit-view.jpg" alt="View from the summit at sunrise" loading="lazy" width="900" height="600">
  <p>The descent took even longer than the climb up.</p>
</article>

Result: The paragraph text renders immediately. The browser reserves a 900-by-600 box for the image (thanks to the width and height attributes) but does not fetch summit-view.jpg until the user scrolls close enough to that part of the article. The image then appears in its reserved space without shifting any surrounding text.

This is the simplest and most common use case: a single content image further down an article. Because it isn’t part of the initial visible content, deferring it reduces the number of network requests competing with the page’s initial render.

Example 2: Lazy-loading an embedded iframe

<section>
  <h2>Watch the Recap</h2>
  <p>Scroll down for the highlight video from this year's conference.</p>
  <iframe
    src="https://example.com/embed/recap-2026"
    title="Conference recap video"
    loading="lazy"
    width="640"
    height="360">
  </iframe>
</section>

Result: The section heading and paragraph render right away. The embedded video player’s iframe document is not requested from example.com until the user scrolls near it, saving the extra network round trips (and often extra scripts and stylesheets) that video embeds tend to load.

Iframes benefit even more from lazy loading than images, because an embedded document often pulls in its own scripts, fonts, and trackers. Deferring an offscreen iframe can meaningfully reduce how much extra content downloads on page load.

Example 3: A realistic photo gallery mixing eager and lazy images

<main>
  <h2>Photo Gallery</h2>

  <!-- Hero image: visible immediately, so it loads eagerly -->
  <img src="gallery-hero.jpg" alt="Wide shot of the canyon at golden hour"
       loading="eager" width="1200" height="500">

  <ul>
    <li>
      <img src="thumb-1.jpg" alt="Hikers crossing a river" loading="lazy" width="300" height="200">
    </li>
    <li>
      <img src="thumb-2.jpg" alt="Campsite at dusk" loading="lazy" width="300" height="200">
    </li>
    <li>
      <img src="thumb-3.jpg" alt="Trail marker on a rocky path" loading="lazy" width="300" height="200">
    </li>
  </ul>
</main>

Result: The wide hero image at the top loads immediately since it’s visible the instant the page renders. The three thumbnail images inside the list are deferred; each one is fetched only as the user scrolls the list into view, so a gallery with fifty thumbnails wouldn’t download all fifty images upfront.

This example demonstrates the key real-world pattern: not every image on a page should be lazy. Content that appears “above the fold” (immediately visible without scrolling) should stay eager so it appears as fast as possible, while everything further down benefits from being lazy.

How it works step by step / Under the hood

  1. The HTML parser encounters an <img> or <iframe> tag and creates the corresponding DOM node right away — the element exists in the document tree immediately, regardless of the loading value.
  2. The browser checks the loading attribute. If it’s missing or set to eager, the resource fetch is queued immediately, just like any other reference.
  3. If loading="lazy" is set, the browser instead registers the element with its internal viewport-distance tracking (conceptually similar to the Intersection Observer API, but built into the rendering engine).
  4. As the user scrolls, the browser continuously checks each deferred element’s distance from the viewport. Once an element crosses the browser’s threshold distance, its resource fetch is finally issued.
  5. When the resource arrives, it’s decoded and painted into the already-reserved space in the layout — which is why declaring width and height (or aspect-ratio via CSS) matters: it lets the browser reserve the correct box during step 1, before the image data even exists, so nothing jumps around when the image finally paints.

Common Mistakes

Mistake 1: Lazy-loading the first visible image

<body>
  <img src="hero-banner.jpg" alt="Site hero banner" loading="lazy" width="1600" height="500">
  <h2>Welcome</h2>
</body>

This markup is technically valid, but it’s a performance mistake: the hero banner is the very first thing a visitor sees, yet loading="lazy" tells the browser to delay it, which can actually make the page’s Largest Contentful Paint slower rather than faster. Above-the-fold content should load eagerly:

<body>
  <img src="hero-banner.jpg" alt="Site hero banner" loading="eager" width="1600" height="500">
  <h2>Welcome</h2>
</body>

Mistake 2: Omitting width and height

<img src="thumb.jpg" alt="Product thumbnail" loading="lazy">

Without explicit dimensions, the browser has no space reserved for this image before it loads. When it finally arrives (potentially seconds after surrounding text has rendered), the page content below it suddenly shifts down — a layout shift that hurts both usability and search-ranking signals like Cumulative Layout Shift. Always pair loading="lazy" with known dimensions:

<img src="thumb.jpg" alt="Product thumbnail" loading="lazy" width="150" height="150">

Best Practices

  • Reserve loading="lazy" for images and iframes that appear below the initial viewport — leave above-the-fold content as eager (or simply omit the attribute).
  • Always include width and height attributes so the browser can reserve layout space and avoid content jumping as lazy images arrive.
  • Always include meaningful alt text on lazy-loaded images; lazy loading changes only when the resource is fetched, not the accessibility requirements of the element.
  • Don’t lazy-load a single small icon or logo — the attribute is meant for content further down a page, not for tiny above-the-fold assets where the overhead outweighs the benefit.
  • Remember loading has no effect on CSS background images; if you need to defer those, that’s a CSS/JavaScript concern, not something HTML alone solves.
  • Test long pages by throttling your network connection in browser dev tools and scrolling through — you should see image requests firing progressively rather than all at once.

Practice Exercises

  1. Build a simple blog article with one hero image at the top and four additional images spaced throughout the body text. Mark the hero image eager and the rest lazy, giving every image explicit width and height attributes.
  2. Create a page with three embedded <iframe> elements stacked vertically (you can point each src at any placeholder URL). Add loading="lazy" to the second and third iframes only, and explain in a comment why the first one should stay eager.
  3. Take an existing image gallery you’ve built in an earlier lesson and add appropriate loading values to every image, deciding for each one whether it belongs above or below the fold.

Summary

  • The loading attribute on <img> and <iframe> lets the browser defer fetching offscreen resources, with no JavaScript needed.
  • loading="lazy" delays the network request until the element nears the viewport; loading="eager" (the default) fetches immediately.
  • The DOM node is created immediately regardless of the loading value — only the resource fetch timing changes.
  • Always pair lazy images with explicit width and height to prevent layout shift.
  • Keep above-the-fold, immediately visible content eager; reserve lazy loading for content further down the page.
  • Lazy loading doesn’t apply to CSS background images or elements without a src.