Skip to Content
srcset and sizes for Product Images That Don't Waste Bandwidth
Advanced
0 likes
Tutorials

srcset and sizes for Product Images That Don't Waste Bandwidth

Measure the real slot width at each breakpoint, write sizes from it, and check which file loads at each screen density.

A product grid is where wasted image bytes pile up: many images, on every page, on phones. The way to write a good sizes is to measure the slot first, not to guess it.

Step 1: measure the slot at each breakpoint

The grid is one column under 600px, two from 600px, and four from 960px, with a 16px gap and 16px page padding. Instead of working out the arithmetic, read the real width in the browser:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    for width in (390, 768, 1280):
        page = browser.new_page(viewport={"width": width, "height": 800})
        page.goto("http://127.0.0.1:8765/srcset.html")
        slot = page.evaluate("document.querySelector('img').getBoundingClientRect().width")
        print(f"viewport {width}px: image slot {round(slot)}px")
    browser.close()
viewport 390px: image slot 342px
viewport 768px: image slot 352px
viewport 1280px: image slot 296px

Slots are narrower than 100vw, 50vw and 25vw because of the padding and gaps. For sizes, the nearest simple value is fine: it is only used to pick a file, not to lay anything out.

Step 2: write sizes from those numbers

<img
  src="product-800.jpg"
  srcset="product-400.jpg 400w, product-800.jpg 800w, product-1200.jpg 1200w"
  sizes="(min-width: 960px) 25vw, (min-width: 600px) 50vw, 100vw"
  width="800" height="480" alt="Blue product tile">

Step 3: check which file loads on each screen

const img = document.querySelector("img");
console.log(img.currentSrc, img.getBoundingClientRect().width, devicePixelRatio);

Run it after resizing the window, or on a phone through remote debugging. The result I got, for the same page at different densities:

390px, DPR 2   slot 342px   -> product-800.jpg
390px, DPR 3   slot 342px   -> product-1200.jpg
1280px, DPR 1  slot 296px   -> product-400.jpg
1280px, DPR 2  slot 296px   -> product-800.jpg

A 342px slot at DPR 2 needs 684 physical pixels, so the 800w file is the smallest that covers it. At DPR 3 it needs 1026, so the 1200w file loads.

What a wrong sizes costs

In an earlier version of the same grid the columns stretched to 608px at 1280px, but sizes still said 25vw. At DPR 1, Chrome picked product-400.jpg and stretched it to 608px, which is a soft image. The same mistake in the other direction, such as 100vw on a small slot, makes the browser download an image that is far larger than it needs.

⚠ Warning: Do not set sizes="100vw" on every image to be safe. It is the most common wrong value, and the cost shows up as megabytes on phone data plans.

Common mistakes

  • Writing sizes once and never updating it when the grid changes.
  • Offering only two widths and asking the browser to cover every density.
  • Using width descriptors that do not match the real file widths.
  • Forgetting width and height, so the grid jumps when each image arrives.

Related course: HTML and CSS Advanced.

About

Measure the real slot width at each breakpoint, write sizes from it, and check which file loads at each screen density.

Responsible
Jay P
Last Update
08/26/2026
Reading Time
3 min
Was this helpful?
​ 0 ​ 0

Comments (0)

Sign in to comment

No comments yet.