Images are notoriously one of the most challenging aspects of responsive web design. Today we'll look at how the <picture>
element, a solution to the problem of responsive images, can be used right now.
First, the Problem
The days of fixed-width, pixel perfect website design are well and truly behind us. In the present day of widescreen monitors, internet TVs, multiple sized tablets and smart phones our designs now have to cater for everything from 320px wide up to potentially as high as 7680px wide.
Along with this multi-resolution landscape comes a need for images to stretch or shrink to fit these wildly varying requirements. This can prove to be something of a problem given that, with the exception of vector graphics, the vast majority of images have specific pixel based widths that do not change.
So what do we do?
The Current, Most Common Solution
As a general rule, you'll find the following in just about any responsive site's CSS:
1
2
3
4
|
img { max-width: 100%; height: auto; }
|
This code uses the max-width: 100%;
setting to ensure images never go beyond the width of their parent container. If the parent container shrinks below the width of the image, the image will scale down along with it. The height: auto;
setting ensures the images' aspect ratio is preserved as this occurs.
It solves the problem in one respect, allowing us to display the same image under many different circumstances. But it doesn't allow us to specify different images for differing circumstances.
A New Solution: <picture>
<picture>
is a new element which is set to become part of HTML5.
It will bring the process for placing responsive images up to speed with the way the current <audio>
and <video>
elements work. It will allow you to place multiplesource
tags, each specifying different image filenames along with the conditions under which they should be loaded.
It will allow you to load an entirely different image depending on:
- Media query results e.g. viewport height, width, orientation
- Pixel density
This in turn means you can:
- Load appropriately file sized images, making the best use of available bandwidth.
- Load differently cropped images with different aspect ratios to suit layout changes at different widths.
- Load higher resolution images for higher pixel density displays.
How Does <picture>
Work?
The basic steps of working with <picture>
are:
- Create opening and closing
<picture></picture>
tags. - Within those tags, create a
<source>
element for each query you want to run. - Add a
media
attribute containing your query on things like viewport height, width, orientation etc. - Add a
srcset
attribute with the corresponding image filename to load. - Add extra filenames to your
srcset
attribute if you want to provide for different pixel densities, e.g. Retina displays. - Add a fallback
<img>
element.
Here's a basic example which checks if the viewport is smaller than 768px, then if so loads a smaller image:
1
2
3
4
5
|
< picture > < source srcset = "smaller.jpg" media = "(max-width: 768px)" > < source srcset = "default.jpg" > < img srcset = "default.jpg" alt = "My default image" > </ picture >
|
You'll notice that the syntax used in the media
attribute is the same as you might be used to from creating CSS media queries. You can use the same checks, meaning you can query max-width
, min-width
, max-height
, min-height
,orientation
and so on.
You can use these checks to do things like loading landscape or portrait versions of an image depending on device orientation, and you can still mix in size queries at the same time. For example:
1
2
3
4
5
6
7
|
< picture > < source srcset = "smaller_landscape.jpg" media = "(max-width: 40em) and (orientation: landscape)" > < source srcset = "smaller_portrait.jpg" media = "(max-width: 40em) and (orientation: portrait)" > < source srcset = "default_landscape.jpg" media = "(min-width: 40em) and (orientation: landscape)" > < source srcset = "default_portrait.jpg" media = "(min-width: 40em) and (orientation: portrait)" > < img srcset = "default_landscape.jpg" alt = "My default image" > </ picture >
|
The above code loads a smaller, landscape cropped version of the image on a smaller, landscape oriented device. It loads a larger version of the same image on a larger landscape oriented device.
If the device is portrait oriented it loads a portrait cropped version, at small size on a small device or at large size on a large device.
If you want to provide different resolution versions of your images for higher density displays, you do so by adding extra filenames to the srcset
attribute. For example, let's look at our first snippet of code from above with handling for Retina's 2x resolution added:
1
2
3
4
5
|
< picture > < source srcset = "smaller.jpg, smaller_retina.jpg 2x" media = "(max-width: 768px)" > < source srcset = "default.jpg, default_retina.jpg 2x" > < img srcset = "default.jpg, default_retina.jpg 2x" alt = "My default image" > </ picture >
|
The media query is still evaluated first so you can control the dimensions your image will appear at on screen. Then the display's pixel density will be checked and if higher densities are both supported and allowed by the user's preferences, the higher density version of image will be loaded.
Using <picture>
Today
Right now native implementation for <picture>
is in the works for Chrome, Firefox and Opera. In the future it's likely we'll see widespread support as other browsers also catch on. But for the moment that support is still yet to arrive.
In the meantime, you don't have to wait if you'd like to start using <picture>
right now. You simply have to use Picturefill 2.0; a polyfill provided by those clever folks atFilament Group.
After downloading the picturefill.js file to your project it can be implemented by simply loading it in your site's head section:
1
|
< script src = "picturefill.js" ></ script >
|
There is also an option to load the script asynchronously for added efficiency, which you can read about in Picturefill's documentation.
With this script loaded, the <picture>
element will work as I've explained, with only a few limitations.
Picturefill Limitations
IE9
Picturefill works just fine with other IE versions, however IE9 doesn't recognise source
elements that are wrapped in picture
tags. To get around this, conditionally wrap your source elements in video
tags which will then make them visible to IE9, for example:
1
2
3
4
5
6
7
|
< picture > <!--[if IE 9]>
|
Android 2.3
Like IE9, Android 2.3 can't see source
elements inside a picture
element. However, it can understand the srcset
attribute when used on a regular img
tag. Be sure to always include your fallback img
element with the default filename in the srcset
attribute for Android 2.3 and any other browsers that may have the same issue.
Requires JavaScript and Native Media Query Support
With this being a JavaScript based solution, it accordingly requires JavaScript to be enabled in the browser. Picturefill 2.0 doesn't provide a "no-js" workaround because if it did, multiple images would appear when native browser support for <picture>
is rolled out. However, you do have the option to use Picturefill 1.2 if a "no-js" option is a must have for you.
The other requirement Picturefill has is for native media query support, to enable the queries in the media
attribute to work. All modern browsers support media queries, with IE8 and lower being the only non-supporting browser with a small remaining user base.