Responsive img gets wrong images from srcset

mike-source picture mike-source · Jul 11, 2018 · Viewed 8.5k times · Source

Tested using Google Chrome in Incognito mode and reloading the page with "empty cache and hard reload" each time.

I have the following html responsive image:

<img class="content-img" src="/app/uploads/2018/07/1400x750.png" 

srcset="/app/uploads/2018/07/1400x750.png 1400w, 
        /app/uploads/2018/07/1400x750-768x411.png 768w, 
        /app/uploads/2018/07/1400x750-1280x686.png 1280w, 
        /app/uploads/2018/07/1400x750-520x279.png 520w, 
        /app/uploads/2018/07/1400x750-420x225.png 420w, 
        /app/uploads/2018/07/1400x750-340x182.png 340w, 
        /app/uploads/2018/07/1400x750-600x321.png 600w" 

sizes="(max-width: 666px) 100vw, (max-width: 1399px) 38vw, 535px" 
>

Expected Behaviour:

1. Viewport Widths 0px - 666px:

  • Browser should take the full viewport pixel width e.g. 450px, and select the smallest src from the srcset where width is greater than 450px, in this case '/app/uploads/2018/07/1400x750-520x279.png'

2. Viewport Widths 667px - 1399px:

  • Browser should take 38% of the viewport width e.g. 380px @ 1000px viewport, and select the smallest src from the srcset where width is greater than 380px, in this case '/app/uploads/2018/07/1400x750-420x225.png'

3. Viewport Widths 1400+ px:

  • Browser should take the default of 535px and select the smallest src from the srcset where width is greater than 535px, in this case '/app/uploads/2018/07/1400x750-600x321.png'

Actual Behaviour:

Testing in Google Chrome, using dev tools inspect element on the img for all of the above examples, the resulting "CurrentSrc" in each case is:

  1. /app/uploads/2018/07/1400x750-520x279.png (CORRECT)

  2. /app/uploads/2018/07/1400x750-1280x686.png (INCORRECT (expecting 420px width)

  3. /app/uploads/2018/07/1400x750.png (INCORRECT (expecting 600px width)

I'm left scratching my head, other similar questions all seem to boil this down to a Google Chrome caching issue, but I've been careful to eliminate that issue when testing and I still don't get the src images I expect.

I'm only 90% sure I've written the correct "sizes" attribute for the behaviour I want. Note that the logic is slightly complex due to lining up with responsive CSS breakpoints and trying to load sensible image widths in context.

Answer

benvc picture benvc · Jul 15, 2018

Some clarification on how the srcset and sizes attributes define the way the browser should choose which image to display (see Responsive images for more details).

First, the browser checks the sizes attribute to find the first media condition that applies to the current device width. So, for the breakpoints you specified, the browser should display the selected image at full viewport width for devices up to 666px wide, then at 38% of viewport width for devices between 667px and 1399px wide, and lastly at a fixed 535px width for devices greater than 1399px wide.

Second, the browser checks the srcset attribute to find the image that most closely matches the image slot size as determined by the sizes attribute (as described above).

So for the breakpoints you specified, expect the following:

1) For devices up to 666px wide, the browser should select the image width that is closest to the device width (not the smallest image that is greater than the device width).

Examples:

On a 450px wide device, the browser should select the 420w image.

On a 599px wide device, the browser should select the 600w image.

2) For devices between 667px and 1399px wide, the browser should select the image width that is closest to 38% of the device width.

Examples:

On a 1000px wide device, the browser should select either the 340w image or the 420w image (not sure how it chooses when you split the difference since the image slot size determined by the media query is 380px)

On a 1366px wide device, the browser should select the 520w image (since the slot size determined by the media query is 519px)

3) For devices over 1399px wide, the browser should select the 520w image (since the slot size determine by the media query is a fixed 535px).

NOTE: Retina and other high-res displays change the math a bit, resulting in the browser more or less doubling the width of image that it chooses in each of the above examples (see Responsive images: if you're just changing resolutions, use srcset).

A couple potential gotchas to double check. Make sure you have <meta name="viewport" content="width=device-width"> in the head so the devices you test on are forced to adopt their real width when loading the page. Also make sure you don't have conflicting css or js interfering with the display of your image.

If you have avoided the gotchas, your code otherwise looks fine and the similar snippet below gives the expected results for me, though you do have to be careful about caching when testing as you already noticed (slightly different organization might help you more quickly scan which images should be chosen under different circumstances). Below is a snippet using placeholder images that show their widths which may help your testing.

<img srcset="https://via.placeholder.com/340x182 340w,
             https://via.placeholder.com/420x225 420w,
             https://via.placeholder.com/520x279 520w,
             https://via.placeholder.com/600x321 600w,
             https://via.placeholder.com/768x411 768w,
             https://via.placeholder.com/1280x686 1280w,
             https://via.placeholder.com/1400x750 1400w"
     sizes="(max-width: 666px) 100vw,
            (max-width: 1399px) 38vw,
            535px"
     src="https://via.placeholder.com/340x182"
     alt="placeholder image">