Responsive images
The <img>
tag all started with this message and has been with us since HTML 2.0. In that time it hasn’t changed much at all, just put a path to an image in the src
attribute and you have an image on the screen.
That all worked very well until the need to provide content to people in many different situations with a wide range of screen sizes presented itself. This need brought with it problems that seemed insurmountable at times and were dismissed as temporary by some, but have now been solved by the extraordinary people and work of the Responsive Images Community Group. I didn’t follow progress closely but I had a passing interest and saw that they sailed many a stormy sea and were slapped by a few broadsides before getting to this point.
The process was sensible; they started with use cases and solved each one. To understand the solutions properly it’s important to understand these use cases, so read them if you’re not sure.
It’s insanely clever how it works and I’m excited about getting it into production sites. It will be used in the next project I start.
A small point of order before going on: I’m using Picturefill 2.0.0 for all the demos as browser support isn’t quite there yet. In order to prevent a double download I’ve left off the src
attribute in some of the demos. That is non-standard and is just to make it easier to see what is downloading in your favourite inspector.
Resolution switching with `srcset`
srcset
is a new attribute for use in <img>
. Its value is a comma separated list of images for the browser to choose from.
Most new phones and tablets are high resolution i.e. they have more than one physical device pixel for every CSS pixel. If we have an image that isn’t going to vary too much or at all in width we can give hi-res screens a hi-res image, and low-res screens a low-res image.
Demo 1 shows an example of this and uses the following markup:
<img srcset="images/low-res.jpg 1x, images/hi-res.jpg 2x" width="320" height="240" alt="In Our Image by Joseph Hiller">
What we’re telling the browser here is that there’s an image to be rendered at 320 CSS pixels wide and 240 CSS pixels high. If it’s a 1x screen use the low resolution image, and if it has a device pixel ratio of 2 or more use the high resolution version.
The browser then requests the correct image from the server. No double download, no media queries, no hacks, just the most appropriate image.
If you don’t have a pair of lo-res/hi-res devices to test on here’s how it looks on an iPhone 3GS and an iPhone 4:
You don’t have to stop at 2x, you can go on as far as you like, future proofing 4x screens for example.
This functionality is supported in Opera since version 21 and Chrome since version 34, so go ahead and use it if you need to. Browsers that don’t support it will fallback to whatever the value of src
is, so no change from what we’re already doing for them.
`srcset` and `sizes`
sizes
is another new attribute that we use to tell the browser what sizes an image is intended be rendered at. I’ll run through a basic example first.
100% wide banner
Demo 2 is a simple example that uses the new srcset
attribute to display different images at different viewport widths. The markup I’ve used is:
srcset="images/small.png 320w, images/medium.png 640w, images/large.png 1024w, images/very-large.png 2000w"
Instead of density we’re listing the images with their pixel widths. Now we can sit back and let the browser figure out which images to load.
Viewport range | Image loaded |
---|---|
0 - 320 pixels | small.png |
321 - 640 pixels | medium.png |
641 - 1024 pixels | large.png |
1025+ pixels | very-large.png |
When I first saw this in action I thought it was perfect for art direction and couldn’t figure out why everyone was saying you need to use <picture>
for that use case. After all I’m swapping out completely different images in the demo.
Here’s another clever bit though, the browser picks the right image, not just for the CSS pixels, but for the device pixels too, so higher resolution screens get a sharper image.
Here’s how demo 2 looks on an iPad 2 on the left and an iPad Air on the right:
The Air has a retina screen so very-large.png is loaded on it. There’s no need on the older iPad so it downloads the smaller image.
It gets even cleverer than that too (in the spec at least). If the browser detects unfavourable network conditions it can ignore the resolution of the screen, prioritise bandwidth over visual quality, and download the smaller image.
No need for the Network Information API and the calculations, assumptions and guesswork that would come with having to cater for bandwidth ourselves. Very useful indeed and great news for users and developers if/when browsers implement it.
Getting back to the example in the demo, I’ve included the sizes
attribute:
sizes="100vw"
This tells the browser that the image will be displayed at 100% of the width of the viewport at all times. 100vw
is in fact the default so it’s not necessary in this case.
Worth pointing out that you can’t use % in sizes
, but you can use em
s which is in the next example.
An image in an article
A common place to see images is at the top of a blog post or page taking up the full width of the main column.
Demo 3 has an image like that in a simple main column/right sidebar layout above 50em (most commonly 800 pixels), all inside a 95% wide wrapper with a maximum width of 75em (most commonly 1200 pixels).
The sizes
attribute is more complex here, but hopefully it illustrates how it applies to images that aren’t always a fixed percentage of the viewport.
The value of srcset
is the same as in the previous demo:
srcset="images/small.png 320w, images/medium.png 640w, images/large.png 1024w, images/very-large.png 2000w"
but we get a bit funky in sizes
.
sizes="(min-width:78.94736842105263em) 48.75em, (min-width:50em) 61.75vw, 95vw"
I’ll take it step by step, starting from the end with the 95vw
.
To be precise what we have is a comma separated list of media query/length pairs. Any value without a media query—in this case 95vw
—is taken as a default and is used when none of the other media queries match. Here, that’s everything below 50em.
We are telling the browser that in viewports between 0 and 50em wide that this image will take up 95% of the viewport. The browser can then select the most appropriate image source from the list in srcset
.
If you start small and resize your browser up you’ll see that it switches from small.png to medium.png to large.png up to 50em, pretty much the way it worked in demo 2. When the viewport is 50em wide something interesting happens. The container holding the image is sized to 65% of the 95% wide wrapper. We need to tell the browser what that is as a percentage of the viewport, and it works out as 61.75% (95*65/100).
Therefore our media query/length pair is (min-width:50em) 61.75vw
.
In a 1x screen with 1em equal to 16 pixels that makes the image render at 494 pixels, so the most suitable image to load is medium.png. It’s a bit like element queries for images right inside in the <img>
tag!
The final item in sizes
takes care of what happens when the viewport is wide enough for the wrapper to be at is maximum width.
(min-width:78.94736842105263em) 48.75em
There’s probably no harm in rounding that to 78.95em, I don’t know, but here’s what’s happening anyhow.
We know that the wrapper div is 95% of the width of the viewport until it is 75em wide, therefore at the point it is exactly 75em wide the viewport is 78.94736842105263em wide.
We also know that the image will be exactly 65% of 75em at this point, which is 48.75em (most commonly 780 pixels) wide, and it won’t get any wider. The very-large.png image will never get loaded.
A width relative to the viewport is no good from here on up so we use an em value instead.
In simple terms all we’ve done is given the browser a list of possible sources, told it their widths, and told it what size the image is to be rendered at different design breakpoints.
It’s useful to play around with the values in sizes
to see how it affects which image is displayed. For example if the largest media query/length pair is removed the browser doesn’t know that the image is 48.75em wide from there on up. It thinks (because we haven’t told it otherwise) that the image is taking up 61.75% of the viewport and will load very-large.png when the window is resized large enough.
The picture element
The new img
attributes will probably be enough for most scenarios, but there are two use cases where extra functionality and a more conditional approach are needed, and that’s where the <picture>
element comes in.
<picture>
is at first glance like <video>
in that it has nested <source>
elements. Unlike <video>
they’re used because they work well to satisfy a use case, not because people couldn’t agree on codecs.
srcset
and sizes
can also be used in <source>
elements.
Art direction
If a photo needs cropped so that its subject is discernible in smaller viewports the picture element is your friend.
Demo 4 is a picture of Bo Diddley. When viewed in a wide viewport you can see quite a bit of the audience. In slightly narrower viewports it would be harder to recognise him so it changes to an image that has Bo taking up most of the frame, and in the smallest viewports it’s just his head.
The markup is:
<source media="(min-width:50em)" srcset="images/bo-and-audience.jpg">
<source media="(min-width:25em)" srcset="images/mainly-just-bo.jpg">
<source srcset="images/just-bos-face.jpg">
<img class="art-directed" src="images/just-bos-face.jpg" alt="Bo Diddley playing live in a small crowded room">
One thing to note is that the order of the sources matters. If you put them the other way round Bo Diddley’s face is all you can see in wider viewports.
Also note that it’s the <img>
that is styled. The <picture>
is only there to provide a src
for the <img>
.
Type switching
WebP can save up to half the bytes of a JPEG or PNG, but it’s only supported in Blink powered browsers for now. Using <picture>
it’s easy to serve WebP to supporting browsers and a fallback to the rest.
<source srcset="image.webp" type="image/webp">
<source srcset="image.png" type="image/png">
Combining `
A more practical example of type switching is in demo 5, which is basically the same as demo 2 but uses WebP with PNG fallbacks. I’ve just popped the srcset
and sizes
into each <source>
with an <img>
fallback. The WebP one is:
<source srcset="images/small.webp 320w, images/medium.webp 640w, images/large.webp 1024w, images/very-large.webp 2000w" sizes="100vw" type="image/webp">
Demo 6 is the blog article from demo 3 with type switching.
Another situation where <picture>
and srcset
are useful together is when you want to give the browser the option to display a high resolution image and still use art direction:
<source media="(min-width:29em)" srcset="small.jpg, small.jpg 2x" sizes="95vw">
So <picture>
is a power up for images that covers use cases srcset
and sizes
alone can’t deal with.
Client hints
Standards solutions to responsive images are amazing, but they still leave us having to create the images at appropriate sizes, resolutions and types.
Client hints are a way to get rid of that manual work and offload it to the server. They are HTTP headers that give the server some information about the device and the requested resource.
The current draft specifies CH-DPR
for device pixel ratio and CH-RW
for resource width (the CSS pixel width of the rendered image).
A server-side script will then generate the best image for the requesting device and send it back.
It’s early days, but client hints are behind a flag in Chrome Canary if you want to play with them, and there’s an extension by Andy Davies for changing the CH-RW and CH-DPR values.
Ilya Grigorik has written a good introduction with some demos and seems to be leading the efforts to make something useful.
Summary
The three technologies do different things.
srcset
and sizes
are saying to the browser, “Here are some images I’ve made and some ideas you’ll find useful, go and figure out what’s best.”
<picture>
on the other hand is saying, “Here’s a list of images I’ve made with the circumstances under which you must use them.”
Client hints say, “Here’s some information, make me the best image to fit in here.”
srcset
and sizes
on an <img>
may be made redundant by client hints, but art direction is subjective and at this point in history necessarily dependant on humans. The <picture>
element will be around for a while yet.
There is one issue with <picture>
, srcset
and sizes
that is kind of concerning, and that is media queries (presentation) in our markup (content), which isn’t separation of concerns by any means. There’s already thought being applied to that problem however, with custom media queries an early candidate for a solution.
So there are drawbacks, minor in my opinion, and I think they have to be viewed in light of two things: the situation before we had proper responsive images, and the megabytes we’re going to save on ordinary websites every hour.
The value of these standards cannot in my view be understated. They are pragmatic, widely applicable and well specified. The problems they solved were really tough ones and they were solved by developers, a point that should not be lost on anyone.
Not only have the RICG made the web better, they have got us, developers, a seat at the standards table. Let’s not waste that, let’s keep working.