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 on360px
screen but this equals 100% width on this mobile phone which is actually1440px
to have a sharp view so I will choose the closest that fits that image size which is1080px
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.
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.
Top comments (3)
Thanks for sharing.
A question, the retina image 1x 2x 3x, are created by browser or are another assets image?
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/
Nice article! These are the HTML features that I always ignore and forget. Worth keeping.