I must open this article with a confession: for a long time the HTML5 responsive images w
descriptor and sizes
attribute left me deeply confused. The art-directed uses of the <picture>
element were obvious, and image-resolution switching with srcset
and the x
descriptor had clear benefits, but when it came to the complex, comma-seperated syntax of sizes
and w
, I was flummoxed. Articles and specifications only deepened the mystery: what on earth were these things for, and why would I want to use them?
After thinking through the issues for some time, and attending a few conference sessions, I started to comprehend why w
and sizes
were so important, and how they might be used… an understanding I hope to share in this and following articles.
Too Much of A Good Thing
Let’s start with an assumption: you want to deliver the best, highest resolution, optimised bitmap image appropriate for the screen that your site is being viewed on.
For the sake of argument, let’s say you have an <article>
column on your site that is 750px wide, with a header “hero” image at the top. The fast, lazy solution would be to create a 1500px wide image, and serve that to everyone:
<article>
<header>
<img src="huge.jpg" alt>
</header>
This is coupled with CSS that will scale the image to always fit the size of the column:
article {
width: 750px;
margin: 0 auto;
}
article header img {
width: 100%; height: auto;
}
The image is twice as wide as its container - 1500px vs 750px - but scaled to fit inside it, meaning that the image’s extra pixel density will make it appear crisp and sharp on Retina / HiDPI screens.
There’s just one problem: huge.jpg is served to absolutely everyone. Those without the benefit of Retina screens receive the same image, and effectively throw away 75% of the pixel information, since their screens can’t gain any benefit from the extra pixels. All of this extra data adds to the file size and load time of the image, slowing down the page: a sacrifice that may be worthwhile for hi-end Retina users, but punishes everyone else.
Improving The Odds
Let’s not take the lazy approach. Instead, we’ll make two versions of the image: huge.jpg will be joined by a 1x version exactly 750 pixels wide. We’ll modify the markup so the browser can decide which one to serve:
<header>
<img src="med.jpg" srcset="huge.jpg 2x" alt>
</header>
Now browsers displaying your site on standard-resolution screens will receive the 750px wide version of the image (med.jpg
), while browsers on HiDPI screens will display the large version. That’s much better… but if you’re doing responsive design, as you should be, there’s an added complication.
The Complexities of Adaptation
Let’s say that our <article>
is not fixed in width, but fluid:
article {
width: 80%;
max-width: 750px;
margin: 0 auto;
}
This CSS is joined by a media query that will make the article the full width of the browser window at small sizes:
body { margin: 0; }
@media all and (max-width: 750px) {
article { width: 100%; }
}
The page still works - both image versions still fit inside the container, thanks to our img
declaration earlier in the CSS - but we have an interesting problem as the browser window gets narrower:
At a certain point, huge.jpg becomes overkill, even on Retina screens: at the low end, we’re pushing a 1500px wide image onto the 360 pixel wide screen of a mobile phone. At that viewport size, using the med.jpg image would make more sense: it has plenty of pixels to provide the smaller HiDPI screen with all the detail it needs, while avoiding the bandwith-hogging file size of huge.jpg.
Unfortunately, there is no subtlety to the “2x” designator: the browser on a Retina device is duty-bound to use a 2x image if it’s available, no matter how counter-productive doing so may be.
Hinting An Image
A big part of the problem is that the browser doesn’t actually know the width of an image until it loads it. That’s exactly where the w
syntax steps in: it provides a cue to the browser as to the size of an image. It looks like this:
<img src="med.jpg" srcset="med.jpg 750w, huge.jpg 1500w" alt sizes="(min-width: 750px), 100vw">
Four notes here:
- You can’t use
w
andx
descriptors together: you must choose one or the other. - Despite the apparent redundancy, the default image must be included in the
srcset
attribute with aw
descriptor if it is to be used effectively. w
andx
cannot be used insrc
: they are only applicable in the context ofsrcset
.- The
sizes
attribute must be used with thew
descriptor (it was not originally required, but has since snuck into the spec).
Working Out Relationships
The w
descriptor provides the actual width of the image in pixels, allowing the browser to determine which image is appropriate to use in which context. If you open this page in Chrome on a Retina screen at a narrow size, for example, you’ll find that the image at the top of the page flicks to the 2x version as you make the browser larger (standard resolution screens will only ever see the 1x version).
Browser Follies
Of course, a developer’s life wouldn’t be complete without a few differences in how browsers currently implement the w
descriptor:
- Chrome will not load the 1x version of an image on a Retina screen if it already has the 2x version. In a way, this makes sense - Chrome is basically saying “I’ve already got plenty of image pixels to work with here, thanks”, but the browser’s behaviour can be a little confusing when you’ve resized it to be as small as possible and are madly refreshing the page without seeing any changes. The only way to absolutely force Chrome to load the 1x version (assuming the page has been previously visited on a wide Retina screen) is to do the following:
- Open the Chrome Developer panel (Command-Option-J)
- Switch to the Network panel
- Right / CMD-click in the panel and choose Empty Cache.
- Switch to the tab holding your page and refresh it, without resizing the browser window.
- Firefox will not load the 2x version of an image dynamically if the browser window starts small and is then made larger. Like Chrome, it will stick with using the 2x version if the browser window starts large and gets small. However, it will load the appropriate version of the image at every size on refresh.
- Safari 8 (mobile and desktop) do not support the
w
syntax, but Safari 9 does, as does Android 40. - Sadly, neither IE nor Edge yet support the
w
descriptor. For them, and older browser versions, you can use the pictureFill polyfill.
How Can I Tell Which Image Version Is Being Used?
Unless you’re prepared to use watermarks like the example at the top of the page, it can be difficult to tell if srcset
and w
are working correctly: after all, the syntax describes the exact same image, just at different resolutions (if you wanted to switch between images that were different in other ways, that’s the role of the <picture>
element). So, aside from trying to spot image quality differences, how can you tell if it’s working?
The first and simplest option is simply to right / CMD-click on the image and choose Save As: the filename in the prompt will correspond to the loaded image version.
Alternatively, in Chrome make sure you’ve completed the process described in “Browser Follies” above, having made the browser window as narrow as possible. Then:
- Refresh the page. You should see that the 1x version of the image load in the Network panel.
- Slowly drag the browser window wider. At a certain point, you’ll see that the 2x version of the image load in the Network Panel, and automatically substituted for the 1x version in the browser window. Neat, right?
When To Use Which
Given the gamut of possible solutions (and various levels of browser support) the choices of responsive image syntax can be overwhelming. Here’s my current state of practice:
- for icons - assuming SVG isn’t appropriate - use the simple
x
descriptor - for header / hero images: use
w
combined withsizes
. - for everything else (smaller illustrations, diagrams etc) deliver a standard-resolution image; try to use SVG if possible.
Another way to consider the difference between x
and w
is that the x
descriptor enhances image quality without regard to quality of service, while w
tries to balance the optimal image quality with performance.
Photograph by Thomas Retz, licensed under Creative Commons
Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/KpJbPR