18

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.

benvc
  • 14,448
  • 4
  • 33
  • 54
mike-source
  • 1,004
  • 3
  • 17
  • 32
  • 1
    It seems that the `px` in `sizes` is affected by the display (e.g. retina). So for example, on a MacBook Pro, when `sizes` is set to `200px`, the browser is actually getting the image with `400w` in `srcset`. I'm not sure if it's the cause of the problem. – He Yifei 何一非 Jul 15 '18 at 03:39
  • Your code works as intended when using non-retina devices. You can test (ref: https://uiux.cc/blog/a-wonderful-way-to-test-your-websites-for-retina-by-google-chrome-without-an-actual-retina-display/). Try use https://www.woutervanderzee.nl/artikelen/responsive-images-srcset-sizes/ – He Yifei 何一非 Jul 15 '18 at 03:57
  • Mike, the issue is that, you are doing the math **ONLY factoring in Pixels**. You also need to consider "**devicePixelRatio**" (how many 'real' pixels are packed into one 'virtual' pixel). A great article to help you debug this: [srcset not working? Getting Wrong Images? Let's Find Out Why!](https://livefiredev.com/srcset-not-working-getting-wrong-images/) Once you do that, everything will work as expected. – Khoj Badami Feb 18 '22 at 13:17

3 Answers3

13

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">
benvc
  • 14,448
  • 4
  • 33
  • 54
  • Here's an example in situ on a wordpress theme, which is where I'm trying to get my responsive images to work: http://staging.what2buy4kids.co.uk/responsive-images-testing/ I used similar placeholders to yours. When I reload the page at 1000px viewport width. The left image and right image both display the 768px wide placeholder. The left image (on that staging link) has `sizes="(max-width: 666px) 100vw, (max-width: 1399px) 38vw, 535px"` (the right has 62vw instead of 38vw). At 1000px wide, I understood this should show the 420px wide placeholder on the left (closest to 380px). – mike-source Jul 18 '18 at 14:06
  • Can you test using that link and see if you can spot the inconsistency? I checked your 'gotchas' and all seemed fine? – mike-source Jul 18 '18 at 14:06
  • I tested on both Chrome and Firefox and found a difference in how each browser handled the side by side images. Chrome seems to identify that both `img` elements share some common `srcset` options, so it just downloads the highest resolution image it needs and uses that for both images. Firefox downloaded and used the different size images (the expected behavior). I suggest testing in Firefox since you get a more "real world" result (assuming that you are not really displaying the same images side by side in different sizes on your site). – benvc Jul 19 '18 at 14:58
2

I have noticed that order matters when it comes to srcset.

For some reason from the biggest image to the smallest one worked, whilst the contrary did not.

I am not entirely sure whether it's an actual thing or not, but I am leaving this out here with the hope it may help someone.

Example that did work:

<img srcset="{{block.settings.slide-image | img_url:'1250x' }} 1227w
{{block.settings.slide-image | img_url:'1100x' }} 1079w,
{{block.settings.slide-image | img_url:'956x' }} 936w,
{{block.settings.slide-image | img_url:'795x' }} 775w,
{{block.settings.slide-image | img_url:'620x' }} 600w,
{{block.settings.slide-image | img_url:'520x' }} 500w,
{{block.settings.slide-image | img_url:'456x' }} 436w"
src="{{block.settings.slide-image | img_url:'456x' }}" />

Example that did not work:

<img srcset="{{block.settings.slide-image | img_url:'456x' }} 436w,
{{block.settings.slide-image | img_url:'520x' }} 500w,
{{block.settings.slide-image | img_url:'620x' }} 600w,
{{block.settings.slide-image | img_url:'795x' }} 775w,
{{block.settings.slide-image | img_url:'956x' }} 936w,
{{block.settings.slide-image | img_url:'1100x' }} 1079w,
{{block.settings.slide-image | img_url:'1250x' }} 1227w"
src="{{block.settings.slide-image | img_url:'456x' }}" />
Diego Fortes
  • 8,830
  • 3
  • 32
  • 42
1

The Missing Ingredient To Understanding What The Browser Is Thinking: devicePixelRatio

The reason this is a source of so much confusion, is that at first glance, we seem to think that all the calculations should be done on the basis of pixels.

Let's walk through the thought process / algorithm of the browser:

  1. How big is the screen? It's a mobile phone: 390px.
  2. How much space does the image occupy? 100vw (100% of the viewport)

If we only factored in this much, we would say: "Okay, use the 400px or 500px image. That should cover it."

But, there is another aspect. Nowadays, phones don't have such low resolutions. They are packed with pixels! This packing is expressed in: devicePixelRatio (can be checked with JS with window.devicePixelRatio)

If you factor that in, you have to ask another question. What is the total number of pixels to be covered:

Phone Width In Pixels x devicePixelRatio

So, that is many more pixels. And that is why the browser chooses the larger image.

Great article which clearly explains all this with images and samples: srcset not working? Getting Wrong Images? Let’s Find Out Why!

What's Going On In The Above Case?

Well, we cannot tell because we don't know what device this code is running on. Or what the device pixel ratio is. Once that is known, I am sure that things will immediately make sense.

Khoj Badami
  • 151
  • 2
  • 7