A Complete Guide to the Element

时间:2020-12-03 16:25:26

If you’ve ever struggled building responsive websites, this post is for you. It’s part of a series on responsive design, in particular responsive images, pulled from my book, Responsive Web Design. If you find this excerpt useful, and want even more ideas on how responsive design can help you create amazing websites, pick up a copy today.

[Last Update: 08/20/2014]

I also wrote up the back story of the <picture> element and all the hard work that made it possible for Ars Technica. If you want to know not just how to use it, but how a small group of people created it, be sure to check that out as well.

Table of Contents

Most people who’ve never heard the phrase before think that “responsive design” refers to building websites that are, well, responsive. That is, fast pages that respond to user input with no lag or discernible load times.

Of course that’s not exactly what the phrase “responsive design” refers to in most web development contexts, but I think the web might be better off if it were. I don’t think we need to throw out Ethan Marcotte’s original definition of responsive design — fluid grids, flexible images and @media queries — but perhaps we could add another criteria to our definition: responsive websites should, above all else, be really, really fast.

There are many, many ways to speed up websites, responsive or otherwise, but few things will lighten the load like reducing image size. If you’ve done nothing yet to optimize the front-end portion of your site, images are almost always the best place to start. Even without responsive images, I managed to shave several seconds off this site’s load time using some very simple, basic optimizations.

Nothing, however, is going to speed up mobile page load times like responsive images. And the good news is, thanks to a lot of hard work from some deditcated developers, responsive images are here.

The <picture> Element

Following the development of <picture> was a bit like listening to Statler and Waldorf in the balcony of the Muppet’s theatre: “I love it!” “It’s terrible!” “It’s brilliant!” “It’s okay” “It could be better” “It’s awful!” “I love it!” And so on as developers and browser makers hashed out the details.

In short, it was a soap opera. But in the end sanity prevailed and it got done. We have a draft specification for a new HTML element — <picture>.

As of right now <picture> is available in the dev channel of Chrome and in Firefox 34+. In both cases you’ll need to enable it. In Firefox, head to about:config and search for “dom.image.picture.enabled”. In Chrome you’ll need to go tochrome://flags/#enable-experimental-web-platform-features, enable that feature and restart.

By the end of the year <picture> support should be on by default in the stable versions of both Chrome and Firefox. More importantly for those of us taking a mobile-first approach to development, <picture. support will be available in the mobile versions as well.

What about other browsers? Opera is based on Blink and will support <picture>(hopefully) when Chrome does. Apple’s Safari supports the srcset portion ofpicture, which we’ll discuss in a minute. WebKit, which powers Safari, will soon have support for the rest of picture, but Apple won’t likely ship it in Safari until the next major update. According to Microsoft’s new Status.Modern.IE site, <picture>support is “under consideration” for a future release.

Fortunately for us, browsers that don’t understand <picture> have a fallback — the good old <img> element.

That means there’s nothing to stop you from using <picture> right now. If you need a solution that works everywhere right now, there’s PictureFill, a JavaScript based polyfill, but it requires JavaScript, which may not be right for every solution. On the plus side, PictureFill only kicks in when the browser doesn’t have native support. Personally, I’m going ahead with straight picture for most clients.

Digging Into Picture

The <picture> element looks a bit like the HTML5 <audio> and <video> tags. The actual <picture> tag acts as a container element for <source> elements which then points to the actual images you want to load.

The big difference is that <picture> doesn’t actually load your image. For that you need an <img> tag. So the browser evaluates all the various attributes you’ve specified in your <picture> block, picks the best image and then loads the image into the<img> inside your <picture> tag.

In other words, <picture> doesn’t actually display your image, it just tells the browser which image to display. Think of it as a way to filter possibilities for the imgtag inside it.

To help the browser pick the best image you have three major components to work with, all attributes of <source> elements within the <picture> tag, except for srcset, which can be an attribute of image as well.

The three attributes are:

  • srcset: Yes, the same srcset that was originally proposed for the <img> tag. Thesrcset attribute gives the browser a list of possible images, along with some (optional) “hints” about the screen resolution and screen size that correspond with each image source.

  • media: The media attribute is where you would put your @media query information. When the @media attribute evaluates to true, the browser then moves to the associated srcset.

  • sizes: The sizes attribute allows you to specify a set of intrinsic sizes for the images described in the srcset attribute. This one is a little tricky at first, but basically it allows you to tell the browser how much of the viewport should be taken up by the image. This will become clearer in the example below.

To get a better understanding of how each attribute works, let’s dive into some code.

Using the <picture> Element for High Resolution Images

Let’s start with the first use case in the responsive images use case list: “resolution-based selection“. Essentially we want to serve high-resolution images to high-res devices while allowing low-res devices to avoid the bandwidth penalty of overly-large files.

Here’s how you would use <picture> to give Hi-DPI screens high-res images and regular screens regular images.

Let’s say we’re trying to build a more responsive version of my Responsive Web Design book page. Let’s say we have two book cover images — cover1x.jpg, which is a normal resolution image, and cover2x.jpg which is the same image, but at a much higher resolution.

Let’s go ahead and make things future-friendly by adding a third image,cover4x.jpg, to handle those 4K+ monitors that are just a few years away from being on every desktop. So with three images at three resolutions our <picture> code would look like this:

<picture>
<source srcset=”cover1x.jpg 1x, cover2x.jpg 2x, cover4x.jpg 4x”>
<img src=”cover1x.jpg” alt=”Responsive Web Design cover”>
</picture>

Here we have a simple <picture> tag with one <source> tag and an <img> tag which doubles as a fall back for older browsers. Within the <source> tag we’ve used thesrcset attribute to say to the browser (or “user-agent” in spec-speak) if the screen pixel density is 1x then load cover1x.jpg; if the screen density is 2x then load the higher-resolution cover2x.jpg. Finally, if the screen density is 4x, grab cover4x.jpg.

What happens if the resolution is somewhere in between these values? Well, you could add in other resolutions (e.g. 1.3x, 1.6x and so on) and URLs if you want to be explicit. Remember though that which image to choose is entirely up to the browser. The srcset values we’ve given are described in the spec as “hints”. It may be that, despite having a high-res screen, the user has explicitly instructed the browser (through a preference setting) not to download large images over 3G.

Screen resolution is after all just one factor in deciding on the appropriate image to download. As developers we don’t (and never will) have all the information that the browser does, which is why the final decision lies with the browser. As I’ve said before, this is a good thing; this is exactly the way it should be.

Here’s the our resolution-based query in action. Provided you’re got a high-res display and are running a browser with <picture> support this should display the high-res image:

A Complete Guide to the <Picture> Element

That’s how you would handle the simple resolution-based selection scenario. Before we move on though, let’s look at another value you can add to srcsetdeclarations: width.

Consider this scenario: we have roughly the same situation, we’ll limit it to two images this time, one high-res, one not. But we don’t know how wide the image is going to be on the user’s screen. Say our normal-res image is 640px wide. On a high-res screen that happens to be only 320 effective pixels wide, a 640px image would actually qualify as a high-res image. The situation is slightly more nuanced than a simple 1x vs 2x screen. To always send the larger image to 2x screens might still waste bandwidth because we’re not accounting for the size of the screen/image.

Here’s how you can handle this scenario with <picture>. Let’s stick with the same assumptions in the last scenario, but let’s be a little more specific this time,cover1x.jpg is 640px wide and cover2x.jpg is 1280px wide. Here’s what the code would look like:

<picture>
<source sizes=”100%” srcset=”cover1x.jpg 640w, cover2x.jpg 1280w”>
<img src=”cover1x.jpg” alt=”Responsive Web Design cover”>
</picture>

Now our srcset values are based on width and the browser gets to select the best image based on another <source> attribute, sizes. In this case we’ve told the browser that final image selected will be as wide as the entire viewport. Later we’ll see how you can use this with other values.

The final result will be as wide as the viewport, so if the user is on a device that is effectively 320px wide, but at 2x density the browser would, barring other conflicting info like user settings, pick cover1.jpg. If the user’s viewport happened to be 640px wide, but the density was only 1x, cover1.jpg would again be used. On the other hand if the viewport happened to be 640px wide, but the density was 2x, cover2.jpgwould be used.

Different Image Sizes Based on Viewport Width

When you think of responsive images, this is probably the use case you think of — serving smaller images to smaller screens, larger ones to larger screens. Later we’ll see how you can combine this with the pixel density stuff above for even more control.

First, here’s how <picture> can be used to serve up different images based on viewport width.

For the following examples, let’s say we have three images, small.jpgmedium.jpgand large.jpg and we want to serve them to the corresponding viewport sizes. Let’s make one more assumption: that we’re taking a mobile-first approach and our fallback will also be the smallest image.

Here’s what that code would look like:

<picture>
<source media=”(min-width: 45em)” srcset=”large.jpg”>
<source media=”(min-width: 18em)” srcset=”medium.jpg”>
<img src=”small.jpg” alt=”Robert Anton Wilson laughing”>
</picture>

This time we’ve used the media attribute to write a couple queries that work just like CSS @media queries. Our mobile-first approach here means any viewport larger than 45em gets large.jpg, any viewport between 18em and 45em gets medium.jpg and anything smaller than 18em gets our small.jpg.

Notice that here our smaller image is in the <img> tag, not a <source> tag. While we could add a third <source> tag with a srcset pointing to small.jpg, there’s no need to do that since, as I mentioned earlier, <picture> and <source> are not the tags that actually load images. The <picture> element must contain an <img> element for the browser to actually display your image. Browsers that understand <picture> will first parse through all your rules, pick an image and then swap that image into the srcattribute on the <img> tag.

In this example not only is the <img> tag a fallback for older browsers, its src value also becomes the image used by <picture> savvy browsers if neither media query evaluates to true.

Here’s the above example in action (wrapped in a figure tag)

A Complete Guide to the <Picture> ElementRobert Anton Wilson. Image from Wikicommons

Different Image Size and Resolution Based on Viewport Width

Now let’s combine both of the previous examples and use <picture> to serve up different size and resolution images based on viewport width and device pixel density. To do that we’ll need six images — small.jpgsmall-hd.jpgmedium.jpg,medium-hd.jpg.large.jpg and large-hd.jpg (side note: in the future you’ll want a CMS that’s good at generating tons of image options from the one you actually upload. Otherwise, plan on going insane while resizing images in Photoshop).

Okay, let’s put all those images into a <picture> tag:

<picture>
<source media=”(min-width: 45em)” srcset=”large.jpg, large-hd.jpg 2x”>
<source media=”(min-width: 18em)” srcset=”medium.jpg, medium-hd.jpg 2x”>
<source srcset=”small.jpg, small-hd.jpg 2x”>
<img src=”small.jpg” alt=”Robert Anton Wilson laughing” >
</picture>

This looks just like the previous example except that now our scrset includes a second image and the 2x value to indicate that our -hd.jpg images are for high resolution screens.

Also note that this time we did use a third <source> tag since small screen devices may still be high resolution. That is, while we don’t need a media attribute, we do want to check the resolution, which requires a third <source> tag.

Solving the Art Direction Conundrum

Here’s a common responsive design problem: You have an image that, at full size on large screens, easily conveys its information. However, when that image is scaled down to fit on a small screen it becomes difficult to understand the image. For example consider an image of the president shaking hands with Robert Anton Wilson. At full size the image might show both men and some background, but when shrunk down you would barely be able to make out that it’s two men shaking hands, let alone have any clue who the men might be.

In situations like this it makes sense to crop the image rather than just scaling it down. In the example above that might mean cropping the image to be just the President and Robert Anton Wilson’s heads. You no longer know they’re shaking hands, but most of the time it’s more important to know who they are than what they’re doing.

Frankly, handling this scenario is really more a problem for your CMS than the<picture> element. But assuming you have a way to generate the cropped image (or images if you’re doing both normal and high-res) then the code would look something like this:

<picture>
<source media=”(min-width: 45em)” srcset=”original.jpg, original-hd.jpg 2x”>
<source media=”(min-width: 18em)” srcset=”cropped-medium.jpg, cropped-medium-hd.jpg 2x”>
<source srcset=”cropped-small.jpg, cropped-small-hd.jpg 2x”>
<img src=”cropped-small.jpg” alt=”The President shaking hands with Robert Anton Wilson” >
</picture>

Here we’re assuming there are two crops that make sense for the viewports they’re targeting. In this case that means a crop that fits viewports between 18em and 45em and another (presumably tighter) crop for smaller screens. We’re also assuming we have both regular and high-resolution versions of the image.

See what I mean about having a CMS that makes it really easy to generate a ton of different images from a single source? Having to do something like this by hand would suck for even the smallest of blogs.

There are other possible scenarios that fit the art direction problem, for example, providing a black and white version of a color pie chart for monochrome screens.

Handling More Complex Scenarios

So far we’ve looked at pretty easy-to-grok scenarios using <picture>, but the new element addresses some more complex situations as well. For example, we might have a responsive layout where images morph depending on viewport width (and thus there may not always be a one-to-one correlation between viewport width and image size).

The <picture> element can handle this scenario as well, but this where the syntax starts to get, well, things can get complicated (as things tend to do when you want them to be very flexible).

Imagine you have a storefront with three breakpoints, one for phone-ish devices, another for tablet-ish and a desktop layout. You build the site using a mobile-first approach, so you start with a single-column layout with images that span the full width of the viewport. At the first breakpoint the images switch to a two-column layout and may be a bit smaller than the full-width, single-column images just before the breakpoint (even though the viewport is larger now). Finally, on the larger layout the images move to a three-column grid and start off at the same size as the two-column layout but then scale up to be as large or larger than the images in the single-column layout.

A Complete Guide to the <Picture> Element

The very common image grid scenario. In this example we’re using a single column (100% width) on small screens, two columns (50% width) on medium screens and three columns (rough 33%, but with some additional padding) on large screens.

So what do we do with this scenario? Again, the first thing you’ll need is a CMS that generates, let’s say six, images to fit this scenario. Assuming the images are in place, the code is actually not that bad, albeit a little verbose. Here’s some example code pulled directly from the responsive images spec:

<picture>
<source sizes=”(max-width: 30em) 100%, (max-width: 50em) 50%, calc(33% - 100px)”
srcset=”pic100.jpg 100w, pic200.jpg 200w, pic400.jpg 400w,
pic800.jpg 800w, pic1600.jpg 1600w, pic3200.jpg 3200w”>
<img src=”pic400.jpg” alt=”Robert Anton Wilson laughing”>
</picture>

Believe it or not, this is actually the terse way to write this out. You could write this out as six different <source> elements each with the entire srcset above, though I have no idea why you would want to do that.

Let’s step through the code line by line. The first thing we do is set up a series of breakpoints along with the size of the image relative to the viewport width at each of those breakpoints. So (max-width: 30em) 100% covers our smaller screen where the layout is single column and the image is full width. Then (max-width: 50em) 50%covers our medium layout which happens between 30em and 50em, where images are now 50% the width of the viewport.

For the last argument in sizes things are a little trickier. There’s no max-width, this just applies to everything over 50em. The single argument uses calc() to say images are 1/3 the viewport width, but there’s 100px of padding as well. You may have heard that you should avoid using calc() in CSS since it tends to slow things down. Is the same thing true here? I actually don’t know; if you do, chime in in the comments.

Once we have the image-to-viewport ratio setup for each of our layout possibilities, then we use srcset to point the browser to our six image sizes, adding a width specification to help the browser pick the best one. In the end the browser will pick the optimal image based on the current image-to-viewport ratio, current viewport size and current viewport density.

Complicated though this may be, it’s actually pretty awesome. You’ve got the ability to serve the right image to the right screen based on the actual size the image will be on the screen. That’s far more effective and powerful than just saying send a small image to a small screen and a big on to a big screen.

Further Reading

Picture is awesome, but sprawling in scope. It’s probably the single most potentially confusing element in HTML, but fortunately the basic uses cases are simple.

Still, it never hurts to have more info. With that in mind here’s a list of tutorials and write ups that you should check out as well.