DEV Community

loading...
Cover image for srcset, sizes, picture, media: What you might not have known.

srcset, sizes, picture, media: What you might not have known.

David Lorenz
Working with Web Technologies since ~20 years now and am seeking for a new challenge ever since. 😍 FinTech | Lead Developer @ Debtvision Previously: FE Lead @ Mercedes-Benz.io
・7 min read

Gosh. Yet another "How-To Responsive Images" article? Well nope, I hope.

The background story: It all started with an <img> tag

So one of my colleagues was going like

WTF Dude, Firefox is loading some bunch of huge images even though I provided a proper srcset and sizes attribute.

In 20 years of programming experience - also in these modern times - me and the team have seen some weird browser bugs here and there so I do not immediately assume the problem is the one in front of the computer.

However in this case, for something so crucial I and rather simple I couldn't believe it's a Browser problem. Especially as Safari was behaving the same way.

It was something like this:

<img src="raw.jpg"  
srcset="360w.jpg 360w, 800w.jpg 800w, 1080w.jpg 1080w" 
sizes="
  (max-width: 360px) 360px,
  (max-width: 800px) 800px,
  (max-width: 1080px) 1080px
">

For a Samsung Galaxy S9 with 360x740 it would load the 1080px one. And we both were a bit confused.

I don't want to create cliffhangers so I will immediately jump to the problem: The S9 has a 4x density as its actual resolution is way more than the above. It is 1440x2960 (h x w). So yeah, divide that by 4 and you will get 360x740.

Now what the browser did was quite clever:

Yo Dog, I know you wanted 360w image on 360px screen but this equals 100% width on this mobile phone which is actually 1440px to have a sharp view so I will choose the closest that fits that image size which is 1080px

It is not directly "intuitive" but it makes sense

It makes sense when you think about it. But for that you have to accept the fact that an img tag with srcset is something where you give the browser options (we will have a closer look on this). You are not necessarily the Director of the scene. If the browser thinks that a certain image out of the provided ones in srcset is the best fit according to the given information then it will choose that image.

So what was the solution now?

We now know what happened and why it happened and I love to tell you the solution - but it really depends on what you want to achieve.

In this specific case he wanted to deliver a specific image depending on the width of the device. So what he could've done (not saying should've done) to deliver the image that he actually wanted is the following:

Before:

<img src="raw.jpg"  
srcset="360w.jpg 360w, 800w.jpg 800w, 1080w.jpg 1080w" 
sizes="
  (max-width: 360px) 360px,
  (max-width: 800px) 800px,
  (max-width: 1080px) 1080px
">

After:

<img src="raw.jpg"  
srcset="360w.jpg 360w, 800w.jpg 800w, 1080w.jpg 1080w" 
sizes="
  ((max-width: 360px) and (-webkit-min-device-pixel-ratio: 4)) 90px,
  ...
  (max-width: 800px) 800px,
  (max-width: 1080px) 1080px
">

So 360/4 = 90px so when the Browser will search for the best image fit now it will go "Okay I need a 4 times bigger image than 90px which is 360px so I am taking the 360w.jpg"

That does not sound like a clean solution does it? Especially when you think about the fact that you would also intentionally deliver an image with non-fitting quality (4 times less the quality to be exact).
However the next time a 5x or 6x DPI device hits the market you need to adapt more and more in your queries of the sizes attribute.

David you talked a lot about the issues but what would you recommend?

I recommend: Start with thinking what you want to see as an end-result. Maybe even draw it on paper. Then continue to think which solution will help you to achieve that and it might not be an img tag with srcset but maybe the picture tag instead. Please read the explanations below for that.

If its the same image on all devices no matter what but with different sizes you should go for using Retina definitions in your srcset:

<img src="raw.jpg"  
srcset="
  image.jpg,
  image@2x.jpx 2x,
  image@3x.jpx 3x,
  image@4x.jpx 4x,
  image@5x.jpx 5x">

It is all about understanding the specs

img: The important specification details on srcset + sizes

If you use srcset="foo.jpg 200w ..." then you MUST (check the W3C specs) provide a sizes attribute to tell the browser "On this media query the image will have a shown size of WHATEVERpx".

If you use srcset="foo.jpg 2x ..." you MUST NOT provide a sizes attribute. The browser would just ignore it as it implies the chosen image only by the pixel density.

According to https://html.spec.whatwg.org/multipage/embedded-content.html#the-img-element (09/2020) the img tag does not support the attributes media, type. Which is understandable if you read the following paragraphs about picture and its source tags.

The powerful picture tag and its source children

Semantically, (reading the W3C specs) the picture element is just a "ghost" container for the img tag that allows to control what image is served in the img tag.

W3C states:

The picture element is a container which provides multiple sources to its contained img element to allow authors to declaratively control or give hints to the user agent about which image resource to use, based on the screen pixel density, viewport size, image format, and other factors. It represents its children.

MDN describes the picture tag as the tag for the goal of Art Direction. What they mean is: Total Flexibility. The big difference to img[srcset][sizes] is that you do not let the browser choose which is the best fit for the device size. You explicitly define what image shall be played under what exact circumstances (media query). This can be based on pixel density, screen ratio, height, whatever you want.

The picture element must at least contain an img tag and for it to be actually useful it should contain 1 or more source tags.

Let us go by examples.

1. Retina Image with picture tag

We simply want to scale up the images depending on device pixel density.

<picture>
 <source srcset="logo.png 1x, logo_x2.png 2x, logo_x3.png 3x">
 <img src="logo.png" alt="Cool Logo πŸ‘Œ">
</picture>

This sample could be seen as over-engineered since removing the picture and source tag but keeping the img tag would do the same thing:

<img src="logo.png" alt="Cool Logo πŸ‘Œ" 
  srcset="logo.png 1x, logo_x2.png 2x, logo_x3.png 3x">

2. Choosing a different image depending on ratio

<picture>
 <source 
  media="(min-aspect-ratio: 21/9)"
  srcset="2100x900px-img.jpg">
<source 
  media="(min-aspect-ratio: 16/9)" 
  srcset="1600x900px-img.jpg">
<source 
  media="(min-aspect-ratio: 1)" 
  srcset="300x300px-img.jpg">
 <img src="fallback_whatever.png" alt="πŸ’ž">
</picture>

The above will solely take the aspect ratio of the screen into account. So for a 600x600 screen with actual resolution of 3000x3000 which would be 5x retina it would still use the 300x300px-img.jpg because that is the first media query that matches.

This is important to note: source tags are matched from top to bottom. If one hits, the following are ignored.

2. Choosing a different image depending on ratio whilst still providing proper retina images

<picture>
 <source 
  media="(min-aspect-ratio: 21/9)"
  srcset="
   2100x900px-img.jpg 1x, 
   4200x1800px-img.jpg 2x,
   6300x2700px-img.jpg 3x">
<source 
  media="(min-aspect-ratio: 16/9)" 
  srcset="
   1600x900px-img.jpg 1x,
   3200x1800px-img.jpg 2x,
   4800x2700px-img.jpg 3x,">
<source 
  media="(min-aspect-ratio: 1)" 
  srcset="
   300x300px-img.jpg 1x,
   600x600px-img.jpg 2x,
   900x900px-img.jpg 3x">
 <img src="fallback_whatever.png" alt="πŸ’ž">
</picture>

The only difference to our previous sample now is that we tell the browser exactly which source tag to take but then the browser is able to choose the best image within this source tag. So if its 8x retina but a squared screen it would hit the last source tag and choose the next best fit which is 3x which is 900x900px-img.jpg.

3. Improving performance with source tag and type attribute

Let us take a simple sample and tune it only for the sake of performance with the type attribute.

Before tuning:

<picture>
 <source 
  media="(min-width: 1024px)"
  srcset="
   big.jpg 1x, 
   big-2x.jpg 2x">
<source 
  media="(min-width: 768px)"
  srcset="
   medium.jpg 1x, 
   medium-2x.jpg 2x">
 <img src="fallback.jpg" 
  srcset="
   fallback.jpg 1x, 
   fallback-2x.jpg 2x" alt="πŸ’ž">
</picture>

As you can see we have 3 potential sources. It is totally okay to also use srcset on the img within a picture tag as it will only be considered when the other 2 source tags are not matched.

The W3C says:

The srcset attribute and the src attribute (if width descriptors are not used) contribute the image sources to the source set (if no source element was selected).

Let's tune:

<picture>
 <source 
  media="(min-width: 1024px)"
  type="image/webp"
  srcset="
   big-optimized.webp 1x, 
   big-optimized-2x.webp 2x">
 <source 
  media="(min-width: 1024px)"
  srcset="
   big.jpg 1x, 
   big-2x.jpg 2x">
 <source 
  media="(min-width: 768px)"
  type="image/webp"
  srcset="
   medium-optimized.webp 1x, 
   medium-optimized-2x.webp 2x">
 <source 
  media="(min-width: 768px)"
  srcset="
   medium.jpg 1x, 
   medium-2x.jpg 2x">
 <source 
  type="image/webp"
  srcset="
   fallback-optimized.webp 1x, 
   fallback-optimized-2x.webp 2x">
 <img src="fallback.jpg" 
  srcset="
   fallback.jpg 1x, 
   fallback-2x.jpg 2x" alt="πŸ’ž">
</picture>

Now we not only provided different images for different resolutions but we also provided the browser the possibility to then take the retina image and of that even the optimized version in the webp format instead of jpg.

picture tag Conclusion

The picture tag allows you to play a different image in different situations. E.g. if you were going to play a moving .gif file but you want to improve A11y you could do something like:

<picture>
 <source 
  media="(prefers-reduced-motion: reduce)"
  srcset="
   no-motion.jpg 1x, 
   no-motion2x.jpg 2x,
   no-motion3x.jpg 1x 3x">
 <img src="funny_moving_gif.gif" alt="Funny Animals">
</picture>

Conclusion: So when to use what?

picture

If you have a complex setup do yourself a favor, use picture.

<img srcset sizes ...>

If you have an image that stays the same ratio but shall be played in different quality versions e.g. actual Photographs such as those uploaded on Instagram or Facebook then you might would want to simplify your life by using <img srcset> or <img srcset sizes> depending on what you want to achieve.

Use the comments below for any questions.

Discussion (3)

Collapse
bonfiglioalessio profile image
<A.BONFIGLIO/>

Thanks for sharing.
A question, the retina image 1x 2x 3x, are created by browser or are another assets image?

Collapse
activenode profile image
David Lorenz Author

You have to provide them with their own custom filepaths. However this often can be simplified using so-called CDN systems e.g. with cloudimage.io/

Collapse
louislow profile image
Louis Low

Nice article! These are the HTML features that I always ignore and forget. Worth keeping.