Lazy Loading Images and Media: How Far Ahead Browsers Fetch

Ben Schwarz

Ben Schwarz

September 28, 2026

In this article

Lazy loading takes one HTML attribute and no JavaScript. On a long page, it skips most of the images, embeds and video a visitor never scrolls to, so the first screen loads sooner and less data is sent.

Most pages don’t use it. Chrome sees loading="lazy" on images for just 26% of page loads, and on iframes in 1.5%.

And 17% of pages lazy-load their LCP image, which makes them slower.

What loading="lazy" and eager do#

loading takes lazy or eager, when unset, eager is the default. The browser’s preload scanner finds an eager image in the HTML and fetches it straight away. A lazy image waits until layout has run and the element enters the viewport (or the user scrolls near to it).

What you can lazy load#

loading="lazy" works on <img>, <iframe>, <video> and <audio> elements:

<img src="my-image.avif" loading="lazy" />
<iframe src="my-slow-iframe.html" loading="lazy" />
<video src="showreel.mp4" loading="lazy" />
<audio src="tunes.ogg" loading="lazy" />

Images and iframes

Widely availableChrome 77+Edge 79+Firefox 121+Safari 16.4+

Video and audio

Limited availabilityChrome 150+Edge 150+FirefoxSafari

What should be lazy loaded?#

Nearly all below the fold media should be lazy-loaded to save on initial page render speed and data transfer.

iFrames are usually a big win with lazy loading, as they have their own internal network cascade and can more grossly affect performance.

Video and audio are newly available. A lazy video holds back its file, its poster and autoplay until it comes close.

How many pages use lazy loading?#

HTTP Archive found loading="lazy" on 44% of pages. Chrome’s feature use counters for September 2026 show lazy loading is barely used for images, and almost never used for anything else:

Element% of Chrome page loads using lazy load
<img loading="lazy">26%
<iframe loading="lazy">1.5%
<video loading="lazy">0.06%
<audio loading="lazy">0.000039%

Support for iFrames arrived after images, and video and audio lazy loading support is new, explaining low usage.

How far ahead each browser fetches lazy images#

Every browser fetches lazy images before they reach the screen, and each one picks a different distance:

Chrome gives video and audio the same distance as images. Firefox and Safari don’t lazy-load them yet, so they load straight away.

BrowserImagesIframesVideo and audio
Chrome, 4G1,250px2,500px1,250px
Chrome, 3G2,500px3,500px2,500px
Chrome, 2G6,000px6,000px6,000px
Firefox600px600pxNot supported
SafariOne viewport0pxNot supported

Three ways lazy loading goes wrong#

Lazy-loading the LCP image#

While lazy-loading is a great feature, but it shouldn’t be thrown around willy-nilly: 17% of Pages lazy load the hero LCP image. Incorrectly adding lazy to your hero image will delay it. Best avoided!

A lazy hero image arrives 262ms later Same image, same page · Chrome 151, Fast 4G CSS applied, layout runs Default (eager) 476ms 177ms loading="lazy" 738ms 439ms waits for layout +262ms
With the default, the LCP image request started at 177ms, while the stylesheet was still downloading. With `loading="lazy"`, it didn’t start until 439ms, just after the stylesheet was applied and layout could run.

On a mobile connection the stylesheet takes longer, and so does the image.

Leaving out width and height#

Lazy media arrives after layout, so it pushes content around unless the browser already knows its dimensions.

Give every lazy image, video and iframe a width and height, or a CSS aspect-ratio. It’s the cheapest Cumulative Layout Shift fix there is.

Lazy-loading everything#

A blanket loading="lazy" in a template can adversely affect page performance, similar to how preloading everything is a bad idea. It’s best to lazy-load below-the-fold elements only.

Anything on the first screen should stay eager.

Where to use lazy, eager and fetchpriority#

Carefully peppering your page with lazy and fetch priority optimisation hints means that browsers will fetch and display content more quickly. Here’s what we recommend:

Which attribute goes where Three rules, from the top of the page down LCP image img img lazy lazy The LCP image fetchpriority="high" Starts early, at top priority Other images on the first screen Nothing to add: eager is the default Images below the first screen loading="lazy"
The LCP image is usually the hero. If a page has no big image up top, skip the first rule.

With <picture>, put loading and fetchpriority on the <img>. The browser ignores them on <source>.

Frameworks like React incorrectly add a preload attribute to every image rendered by SSR. You can opt out of this behavior by adding lazy-loading, which is also counter-intuitive. Always audit the actual HTML rendered by your server.

Lazy rendered content#

loading="lazy" defers downloads, but you can also lazy render content in your pages for an additional speed boost.

CSS content-visibility#

Content visibility (content-visibility: auto) skips layout and paint for content that’s off-screen.

Newly availableChrome 108+Edge 108+Firefox 130+Safari 26+

This saves time for needless rendering and painting. In web.dev’s travel blog example, rendering time dropped from 232ms to 30ms.

Pair it with contain-intrinsic-size, so a skipped section keeps a placeholder height and the scrollbar doesn’t jump:

.article-section {
	content-visibility: auto;
	contain-intrinsic-size: auto 500px;
}

Skipped content stays in the DOM and the accessibility tree, so find-in-page and keyboard navigation still work.

JS IntersectionObserver: lazy anything!#

You can extend other areas of your page to respond when content is near-to or in the user’s viewport using an IntersectionObserver, which is well supported:

Widely availableChrome 58+Edge 16+Firefox 55+Safari 12.1+
  • Widgets that do more than fetch a file: a map, a chat widget
  • CSS background images, which loading doesn’t apply to
  • Video and audio in Firefox and Safari, which don’t support loading on them yet. web.dev shows how to start video with an observer
  • JavaScript you only need when a section appears, loaded with import()

Using rootMargin you can customise how near-to the viewport content has to be before it’s initiated.

// Load map 600px before user scroll
const observer = new IntersectionObserver(
	(entries) => {
		for (const entry of entries) {
			if (!entry.isIntersecting) continue;
			import("./map.js").then(({ mountMap }) => mountMap(entry.target));
			observer.unobserve(entry.target);
		}
	},
	{ rootMargin: "600px" },
);

observer.observe(document.querySelector("#map"));

True story: Most LLM Agents or Bots don’t scroll the page, so you can effectively hide content using IntersectionObserver to protect against abuse, and offer a superior user experience. Neat!

How to check lazy loading is working#

In DevTools:

  1. Open the Network panel and filter to Img and Media. Iframes show up under Doc.
  2. Tick Disable cache, reload, and don’t scroll. That list is everything fetched during load.
  3. Now scroll. Whatever arrives is what was deferred.

Check the Elements panel too, not just your templates. The attribute often gets added somewhere you didn’t write it.

In conclusion#

You can positively influence user-experiences by paying careful attention to how content is fetched and loaded using <link rel="preload">, loading="lazy" and fetchpriority attributes, but if you get it wrong, performance can suffer. Getting it right is almost a free win.

Ben Schwarz

Ben Schwarz

Ben is the Founder and CEO of Calibre. He uses his experience in far-reaching Open Source projects and web standards to build tools for a better, more accessible web. Find him on Bluesky Mastodon or LinkedIn.