WSS
Web Specification Studio Home
On this page
PerformanceRecommendedUpdated

Lazy loading images, iframes, and video

Native lazy loading defers off-screen images, iframes, and (recently) video until the user scrolls near them. Use loading="lazy" - but never on the LCP element.

What it is

The loading attribute lets the browser defer fetching a resource until it is close to the viewport. It is a single HTML attribute - no JavaScript, no IntersectionObserver. It applies to three elements:

<img src="diagram.webp" width="800" height="600" loading="lazy" decoding="async" alt="…">
<iframe src="https://www.youtube.com/embed/…" loading="lazy" title="Demo"></iframe>
<video src="clip.mp4" loading="lazy" controls width="800" height="450"></video>

Valid values: lazy (defer), eager (load immediately - the default).

<img> has supported loading="lazy" since Chrome 77 (2019) and is universally available across modern browsers. <iframe> followed shortly after. <video loading="lazy"> is the newest member - added to the HTML Living Standard in 2026 and shipping in Chromium-based browsers; treat it as progressive enhancement, since older browsers simply ignore the attribute and fall back to the element’s existing preload behaviour.

Why it matters

A typical article page has a dozen images below the fold that the user may never scroll to. Loading them eagerly costs bandwidth, blocks more important requests, and steals decode time from the main thread. Lazy loading defers that work, often cutting initial bytes by 30–60% on long pages.

decoding="async" is complementary: it lets the browser decode the image off the main thread, so the image arriving doesn’t jank scrolling or interactions.

How to implement

Default off-screen images to lazy. Anything below the fold - gallery thumbnails, author avatars, embedded tweets, YouTube embeds - should be loading="lazy".

Keep the LCP image eager. The hero image, the article lead, the product photo - these must not be lazy-loaded. Browsers honour loading="lazy" even when the image is just below the fold on a small viewport, and the LCP cost is significant. When in doubt, use loading="eager" (or omit the attribute) for above-the-fold images.

Add decoding="async" to all images. It has no downside and helps with main-thread responsiveness.

Lazy-load iframes by default. YouTube and map embeds pull hundreds of kilobytes of JavaScript on every page view. loading="lazy" on the iframe defers all of it.

Lazy-load below-the-fold video. Combine loading="lazy" with preload="none" (or preload="metadata" if you need the poster frame) so neither the video bytes nor its metadata are fetched until the user reaches it. In older browsers without loading="lazy" support, preload alone still gives you most of the win.

<video loading="lazy" preload="none" poster="poster.webp" controls
       width="800" height="450">
  <source src="clip.av1.mp4" type="video/mp4; codecs=av01.0.05M.08">
  <source src="clip.h264.mp4" type="video/mp4; codecs=avc1.42E01E">
</video>

Keep width and height. Lazy loading does not change layout - without dimensions you still get CLS when the resource arrives.

Common mistakes

  • loading="lazy" on the LCP image. The single biggest lazy-loading mistake. Lighthouse and PageSpeed Insights flag it explicitly.
  • Custom JavaScript lazy-loaders running on top of native lazy loading, doubling the work.
  • Lazy-loading every image to “be safe” - the first screenful must load eagerly.
  • Omitting dimensions, so lazy-loaded images shift the layout on arrival.

Browser Thresholds & Container Scrolling Edge Cases

Understanding browser-native fetch triggers and scroll container boundaries ensures predictable lazy-loading:

  • Browser Fetch Distance Thresholds: Browsers do not wait until an image is 0px from the viewport to start fetching. On fast 4G/5G connections, Chrome triggers fetches when an image is within 1250px of the viewport; on 3G connections, the threshold increases to 2500px to ensure the image finishes loading before scrolling reaches it.
  • Scroll Container Issues (overflow: scroll): Native loading="lazy" evaluates distance against the root document viewport. If images reside inside nested scrollable elements (overflow-y: auto or overflow-x: scroll), native lazy loading can fail to trigger or trigger prematurely depending on browser engine support. For nested carousel containers, test scroll behavior explicitly.
  • Dynamic Content Injection & DOM Mutations: Images inserted into the DOM via JavaScript after initial parse honor loading="lazy" immediately based on their layout position relative to the current scroll offset.

Verification

  • DevTools → Network → throttle to “Slow 4G”, reload, watch which images fetch immediately and which wait for scroll.
  • Lighthouse audit: “Largest Contentful Paint image was lazily loaded” is a hard failure to fix.
  • View source: every below-the-fold <img> and <iframe> should have loading="lazy".

Related topics

Sources & further reading