Image Load Lab

Guides Browser Behaviour Tested Oct 17, 2026 Chrome 155 · 14 cases · 2 viewports

Do Hidden Images Still Download? display:none and Lazy Loading in Chrome

Every hidden image without loading=lazy downloaded in full in Chrome 155, even inside display:none. Lazy loading stopped the download for five of six hiding methods, but not for visibility:hidden.

Hiding an image with CSS is one of the most common responsive tricks: a large photo for desktop, display: none on phones, and a smaller one shown instead. The visitor never sees the hidden image. Does Chrome still download it? We built fourteen small test pages, each hiding the same 800-pixel JPEG in a different way, and recorded every network request Chrome made while loading them.

The test

Each page contains a line of text, the hidden image and another line of text. The image is the same 800 × 600 JPEG of a CC0 market photo in every case, a 53.9 KB file, with a unique query string so that each request can be identified. Byte figures below are transfer sizes as reported by Chrome, which add about 180 bytes of HTTP headers (54.1 KB for this image). The hiding methods are: display: none on a parent or on the image itself, visibility: hidden, the hidden attribute on a parent, a media query that hides the image below 700 pixels, a <picture> inside a hidden parent, opacity: 0 and absolute positioning 9,999 pixels to the left of the page. Six of the cases were repeated with loading="lazy".

Pages were served from a local server and loaded in Chrome 155.0.8059.82, driven by Playwright, with the browser cache disabled. Every page was loaded three times with a phone viewport (390 pixels wide, 3× density) and three times with a desktop viewport (1440 pixels). Requests and transferred bytes were read from the Chrome DevTools Protocol. All three runs gave the same answer in every case. The general setup is described in our testing methodology.

Results

Bar chart: every hidden image without loading=lazy cost 54.1 KB on a phone, while the lazy versions inside display:none, a hidden parent, a media query or off-screen cost nothing; visibility:hidden with lazy still downloaded
Kilobytes transferred for one hidden 800-pixel JPEG on a 390-pixel phone viewport, by hiding method. Grey bars are zero. Chart generated from the linked CSV.
Each page loaded 3 times per viewport; every run gave the same result
How the image is hidden Phone (390 px) Desktop (1440 px) Priority when requested
Parent display:none Downloaded (54.1 KB) Downloaded (54.1 KB) Medium
Parent display:none + lazy Not downloaded Not downloaded –
img display:none Downloaded (54.1 KB) Downloaded (54.1 KB) Medium
img display:none + lazy Not downloaded Not downloaded –
visibility:hidden Downloaded (54.1 KB) Downloaded (54.1 KB) Medium
visibility:hidden + lazy Downloaded (54.1 KB) Downloaded (54.1 KB) Low
Parent hidden attribute Downloaded (54.1 KB) Downloaded (54.1 KB) Medium
Parent hidden attribute + lazy Not downloaded Not downloaded –
Hidden by media query Downloaded (54.1 KB) Downloaded (54.1 KB) Medium
Hidden by media query + lazy Not downloaded Downloaded (54.1 KB) Low
<picture> in display:none Downloaded (24.2 KB) Downloaded (24.2 KB) Medium
opacity:0 Downloaded (54.1 KB) Downloaded (54.1 KB) Medium
Positioned off-screen Downloaded (54.1 KB) Downloaded (54.1 KB) Medium
Positioned off-screen + lazy Not downloaded Not downloaded –

Without loading="lazy", hiding did not save a single byte. All eight eager cases downloaded the full image on both viewports: an image inside a display: none parent, an image with display: none itself, the hidden attribute, a media query, opacity: 0 and an off-screen position all cost the same 54.1 KB as a visible image. Chrome even gave these requests its normal “Medium” priority. The <picture> element inside a hidden parent also downloaded its preferred source, the 24.2 KB AVIF.

With loading="lazy", five of the six hiding methods stopped the download. An image hidden by display: none (on itself or on a parent), by the hidden attribute, by a media query on the phone viewport, or pushed off-screen was never requested during the three-second observation window. The exception was visibility: hidden: that image still occupies its place in the layout, inside the first screen, so lazy loading treated it as visible and fetched it, at “Low” priority.

The media-query case shows both sides on the desktop viewport. There the image was not hidden at all, so the lazy version downloaded normally, exactly as it should.

Why this happens

An <img> element starts fetching its file as soon as the parser sees its src, long before CSS decides whether the element will be rendered. Chrome’s preload scanner even finds image URLs before the stylesheet has arrived. CSS can hide the result, but by then the request is already on its way. Lazy loading changes the rule: the browser waits until the element’s box is near the viewport. An element with display: none has no box at all, so it never gets near the viewport and is never fetched. An element with visibility: hidden or opacity: 0 keeps its box, so it is fetched like any visible image.

CSS background images behave differently, because the stylesheet decides whether they are needed at all. We tested that separately in which hidden CSS background images Chrome downloads.

What to do with this

  • “Desktop image, hidden on phones”: if both images are in the HTML as eager <img> elements, phones download both. Use one <picture> element with a media condition instead, so only one file is chosen, or add loading="lazy" to the image that may be hidden.
  • Tabs, menus and accordions built with display: none or hidden: give their images loading="lazy" and they will load only when the panel opens. Our test of images in closed details and dialog elements shows the same pattern.
  • Do not rely on visibility: hidden or opacity: 0 to save bytes. Both download the image even when it is lazy.
  • Do not lazy-load the main image just to be safe. Lazy loading an image in the first screen delays it; our lazy-loading test measured 160–244 ms of extra Largest Contentful Paint.

Practical conclusion

In Chrome 155, CSS alone never prevented an <img> download: eight hiding methods all transferred the full file. If an image might stay hidden, mark it loading="lazy", which avoided the download for display: none, the hidden attribute, media-query hiding and off-screen positioning, but not for visibility: hidden. Better still, do not send markup for images that a layout will never show.

Limitations

We tested Chrome only; Firefox and Safari implement the same HTML rules but may schedule requests differently. The hidden image sat in the first screen of a short page; a visibility: hidden image far below the fold would follow the normal lazy-loading distance instead. We observed each page for three seconds after load, without scrolling.

Data and files

Download hidden-images.csv (28 rows, 1.3 KB) · Method: testing methodology. Photo: image credits.

Client Hints on Image Requests: What Chrome Sends

Chrome 155 sent DPR, width and network hints with image requests only after Accept-CH, and to another origin only with Permissions-Policy delegation. Width was in device pixels.

Browser Behaviour 4 min read

Video Poster Images and LCP: Same as an img?

A video poster was discovered as early as an img, got the same priority and counted as the LCP element with the same timing, 1,316 ms against 1,320 ms. It cannot use srcset.

Browser Behaviour 3 min read