How to Build Responsive Images with srcset
This article is part of a web development series from Microsoft. Thank you for supporting the partners who make SitePoint possible.
In this connected world where people have a variety of devices, ensuring your website works seamlessly across all of them is a need, not an option. Your website views won’t come from a single resolution device, or a single form-factor. To cater to all of them, your website needs to be responsive.
If you’re familiar with responsive web design (RWD), you probably know the intricacies that come with a responsive design (and how Bootstrap solves a lot of them!). An important part of a responsive website are responsive images. In this article, we’ll learn more about responsive images on the web and see how to build them.
What is a Responsive Image?
In simple terms, a responsive image is an image which is displayed in its best form on a web page, depending on the device your website is being viewed from. The term ‘best form’ could mean multiple things:
- You want to show a separate image asset based on the user’s physical screen size. For example: you want to show a separate image asset on a 13.5 inch laptop and a 5 inch mobile phone (on a maximized browser).
- You want to show a separate image based on the resolution of the device (or, the device-pixel ratio, which is the ratio of device pixel and CSS pixel).
- You want to show an image in a specified image format (JPEG XR, for example) if the browser supports it, probably because of the higher compression that format supports.
The building blocks for responsive are included in most modern browsers including Microsoft Edge (starting Windows Insider Build 10547). You can view the Web Platform status of features like
How to Enable Responsive Images
There are a number of ways to enable responsive behavior of images. One of the older methods (not recommended) is by simple scripting, but this leads to a couple of problems. One, if a script determines which image to download, but the script itself is loaded after the images specified in the HTML have been downloaded, you may potentially end up with two downloaded images. Two, if you don’t specify any image in HTML and want to load only the image as defined by the script, you’ll end up with no image at all for the browsers which have scripting disabled.
Hence, we need a better way to deal with responsive images. And thankfully, there is! The recommended way is to use:
Let’s delve a little deeper.
Before we explore how
srcset is actually used, let’s understand a few terms:
Device-pixel ratio is the number of device pixels per CSS pixel. Two key conditions contribute to device-pixel ratio:
- Pixel density of the device (number of physical pixels per inch): A high resolution device will have a higher pixel density and hence, for the same zoom level, it will have a high device-pixel ratio as compared to a lower resolution device.For example: A high-end Lumia 950 phone would have a higher resolution than a budget Lumia 630 phone, and hence it will have a higher device-pixel ratio for the same zoom level.
- Zoom level of the browser: For the same device, a higher zoom level means more number of device pixels per CSS pixel, and hence a higher device-pixel ratio. For example, consider this figure:When you zoom in on your browser (Ctrl + Plus), the number of CSS pixels for your div remains the same, but the number of device pixels it occupies increases. So, you have a higher number of device pixels per CSS pixel.
When you want to display separate images (or usually, a separate asset of the same image) based on the device-pixel ratio, you’d go with basic
<img src="images/space-needle.jpg" srcset="images/space-needle.jpg 1x, images/space-needle-2x.jpg 2x, images/space-needle-hd.jpg 3x">
x descriptor in the
srcset attribute is used to define the device-pixel ratio. Hence,
- for a device-pixel ratio of 1, the image space-needle.jpg will be used.
- for a device-pixel ratio of 2, the image space-needle-2x.jpg will be used.
- for a device-pixel ratio of 3, the image space-needle-hd.jpg will be used.
src attribute is used as a fallback for the browsers which do not yet support
This works well. Using the
x descriptor, you’ll always get the same image on the devices with similar device-pixel ratio – even if this means that you get the same image on a 13.5 inch laptop and a 5 inch mobile phone which have the same device-pixel ratio.
Now suppose we want a different size (height, width) image on a larger or smaller viewport. This is where the
w descriptor in
srcset and a new attribute –
sizes comes into play.
- w descriptor: This describes the width of the image being referenced. Consider this example:
<img src="images/space-needle.jpg" srcset="images/space-needle.jpg 200w, images/space-needle-2x.jpg 400w, images/space-needle-hd.jpg 600w">
- This mentions that the width of the first image is 200px, second image is 400px, and third image is 600px. Also, if the user’s screen is 150 CSS pixels wide, this equates to the following in terms of
<img src="images/space-needle.jpg" srcset="images/space-needle.jpg 1.33x, images/space-needle-2x.jpg 2.67x, images/space-needle-hd.jpg 4x">
(Remember, device-pixel ratio is just number of device pixels/CSS pixels.)
The actual implementation where you’d want a different size image (different height, width) on different screen sizes is accomplished by using
sizes attribute along with the
w descriptor of
srcset attribute. Let’s again learn through a couple of examples:
Say you want the image to be viewed in half of the viewport width. You’ll type:
<img src="images/space-needle.jpg" sizes="50vw" srcset="images/space-needle.jpg 200w, images/space-needle-2x.jpg 400w, images/space-needle-hd.jpg 600w">
The browser will now decide which image to download based on the browser width and the device pixel ratio. For example:
- If the browser width is 500 CSS pixels, the image will be displayed 250px wide (because of 50vw). Now, this is equivalent to specifying:
srcset="images/space-needle.jpg 0.8x, images/space-needle-2x.jpg 1.6x, images/space-needle-hd.jpg 2.4x"
- So, for a 1.5x display,
images/space-needle-2x.jpgwill be downloaded by a browser, since it gives a device-pixel ratio of 1.6x (which is most suitable for a 1.5x display).
You want the image to be displayed in half of the viewport width when the viewport width is greater than 40em, but the image should occupy the complete width when the viewport width is less than or equal to 40em. This is how you’ll approach it:
<img src="images/space-needle.jpg" sizes="(max-width: 40em) 100vw, 50vw" srcset="images/space-needle.jpg 200w, images/space-needle-2x.jpg 400w, images/space-needle-hd.jpg 600w">
This is very similar to media queries. So, for a viewport which is
(max-width: 40em) evaluates to true, which means
100vw, that is, the image is as wide as the viewport. If the viewport is the browser window and if the browser width is 500 CSS pixels, the image will be displayed 500px wide. This is equivalent to specifying:
<img src="images/space-needle.jpg" sizes="(max-width: 40em) 100vw, 50vw" srcset="images/space-needle.jpg 0.4x, images/space-needle-2x.jpg 0.8x, images/space-needle-hd.jpg 1.2x">
Similar to above, the browser will decide which image to pick from above for a specific display.
For a viewport which is
(max-width: 40em) evaluates to false, which means
50vw, that is, the image is half as wide as the viewport.
The next element we’ll see is the
picture element, but before that I need a quick recap :)
|I want the same image to be displayed across all devices, but I want to display it in a higher resolution on devices which can support it. The height and width of the image should remain fixed.||Make multiple assets of the same image (space-needle.jpg, space-needle-hd.jpg). Use
|I want the same scenario as above, but I should be able to customize height and width of the image based on the viewport.||Use
|I’m doubtful that if I use the same image for a smaller screen size, the primary subject of my image may become too small in size. I want to display a different image (more focused on the primary subject) in a different screen size, but I still want to display separate assets of the same image based on device-pixel ratio, and I want to customize height and width of the image based on viewport.||?|
The solution to ‘?’ is
As we saw above,
picture element is used when you want to show a different image depending on the rendered size of the image. The
picture element is a container which contains other elements that control the image to be downloaded. Let’s look at an example:
<picture> <source media="(max-width: 20em)" srcset="images/small/space-needle.jpg 1x, images/small/space-needle-2x.jpg 2x, images/small/space-needle-hd.jpg 3x"> <source media="(max-width: 40em)" srcset="images/medium/space-needle.jpg 1x, images/medium/space-needle-2x.jpg 2x, images/medium/space-needle-hd.jpg 3x"> <img src="space-needle.jpg" alt="Space Needle"> </picture>
source element whose
media attribute’s media query holds true is chosen.
So, if the max width of viewport is
20em, the corresponding image source is selected from the
images/small directory, based on the device-pixel ratio. All the images within one
srcset are usually multiple assets of the same image.
picture element itself does not display anything. Even the
source element within the
picture element does not represent anything of its own. The
source element must contain the
srcset attribute and it may have
type attributes. It’s necessary to add
img element within
picture. You won’t see any image without the
img element. All the
source elements within the
picture element are just there to feed the image a source.
You can also do an image selection based on the image format supported by the browser. This is especially useful when there are good savings on the image size just based on the format. For example, JPEG-XR, an efficient image format, which usually takes less image size as compared to JPG, is supported by Microsoft Edge and IE9+. Using
type attribute within the
source element, you can test for this format:
<picture> <source media="(max-width: 30em)" type="image/vnd.ms-photo" srcset="images/small/space-needle.jxr 1x, images/small/space-needle-2x.jxr 2x, images/small/space-needle-hd.jxr 3x"> <source media="(max-width: 30em)" type="image/jpg" srcset="images/small/space-needle.jpg 1x, images/small/space-needle-2x.jpg 2x, images/small/space-needle-hd.jpg 3x"> <img src="space-needle.jpg" alt="Space Needle"> </picture>
When it is used, both the attributes:
media attribute and the
type attribute should resolve to true for that
source element to be chosen. If the browser can’t understand any of the formats, it falls back to
Putting It All Together
After understanding how responsive images work, let’s look at a complete example which utilizes all the three together –
<!DOCTYPE html> <html> <head> <title> Responsive images are here! </title> </head> <body style="width:100%"> <picture> <source media="(max-width: 700px)" sizes="(max-width: 500px) 50vw, 10vw" srcset="stick-figure-narrow.png 138w, stick-figure-hd-narrow.png 138w"> <source media="(max-width: 1400px)" sizes="(max-width: 1000px) 100vw, 50vw" srcset="stick-figure.png 416w, stick-figure-hd.png 416w"> <img src="stick-original.png" alt="Human"> </picture> </body> </html>
In this example, we’ve used
picture which contains multiple
source elements. The first one is chosen if the max width is 700px. If this is chosen, the
sizes attribute then decides the size of the image to be displayed based on the width breakpoints we have mentioned. The implementation is exactly similar to what we have seen in
sizes attribute. So, if the max width is 500px (width ranges from 0px-500px), the image will occupy half of the viewport. And the image source is chosen based on the device-pixel ratio. However, if the viewport width is greater than 500px (but
<=700px, because we are within the first
source element), then the image will occupy just 1/10 of the viewport.
Similarly, the second
source element is chosen if the max width is 1400px (which means now the width ranges from 701px to 1400px). The
sizes attribute ensures that if the viewport width ranges from 701px to 1000px, the image’s width is same as the viewport width, and if the viewport width ranges from 1001px to 1400px, the image occupies half of the viewport width. To verify all the cases, I simply resize the browser and check. In real-world use, your website will be accessed through different devices and that is where you’ll see the actual use of responsive images. Here is the output:
(Please note all the image sources and the image width set here are just for the demo, and as you can see, I make some funny stick figures!)
Viewport width set between 1001px and 1400px: the image width is half of the viewport width (Image source being used is stick-figure.png)
Viewport width set between 701px and 1000px: the image width is same as the viewport width. (Image source being used is stick-figure.png)
Viewport width set between 501px and 700px: The image width is 1/10 of the viewport width. (Image source being used is stick-figure-narrow.png)
Viewport width set between 0px and 500px: The image width is half of the viewport width. (Image source being used is stick-figure-narrow.png)
And We’re Almost Done!
Responsive images give you, the developer, an option to give the best experience to your users across the multiple devices they use to view your website. It’s a good time to start integrating it into your website if you haven’t yet.
I hope you had fun reading this! Reach out to me on Twitter if you want to discuss more!
More Hands-on with Web Development
We encourage you to test across browsers and devices including Microsoft Edge – the default browser for Windows 10 – with free tools on dev.modern.IE:
- Scan your site for out-of-date libraries, layout issues, and accessibility
- Use virtual machines for Mac, Linux, and Windows
- Remotely test for Microsoft Edge on your own device
- Coding Lab on GitHub: Cross-browser testing and best practices
In-depth tech learning on Microsoft Edge and the Web Platform from our engineers and evangelists:
- Woah, I can test Edge & IE on a Mac & Linux! (from Rey Bango)
- The Edge Rendering Engine that makes the Web just work (from Jacob Rossi)
- Unleash 3D rendering with WebGL (from David Catuhe including the vorlon.JS and babylonJS projects)
- Hosted web apps and web platform innovations (from Kevin Hill and Kiril Seksenov including the manifold.JS project)
More free cross-platform tools & resources for the Web Platform: