Handling responsive images in HTML
Introduction
At Le Collectionist we rent luxury villas, so the quality of our images is very important. We want our customers to envision themselves in our houses through beautiful images, and for that we need to display the highest quality images that are still performant and fast to load.
In this article, you’ll find all the knowledge I gathered about responsive images in HTML. I’ll explain why we started inquiring about how to improve our images. I’ll describe the different kind of responsive images you may encounter on the web or on responsive UI designs, and the HTML tools to handle them: srcset attribute and picture element.
I hope the examples will help you understand how the browsers chooses an image among the different sources you provide. This article is quite long but I wanted to make detailed explanations about all the things that took me a long time to figure out. At the end, you’ll find a summary to help you decide what HTML you need for your use case.
TL;DR — Go to Graphic Summary
I. How it all started : our first problems with images
We started working on the product page describing one house, with many pictures and details about the house.
We displayed a carousel with all the images of the house.
Since the file size of a photo can be up to several Mo, we first decided to set an arbitrary width of a 1000px on all of our images, so that they weren’t too big and too long to load. The HTML pretty much looked like this:
But it didn’t look really good, it was a bit grainy especially on Macbook retina screens (most of our users are on Apple devices). We also displayed the photos in fullscreen, and the result was quite bad on laptop compared to the original source photo.
Based on this observation, I started to inquire about how to handle responsive images, with two main goals:
- having outstanding images on retina laptops (the vast majority of our users are on iOS/macOS)
- avoid loading heavy images on smartphones that would slow down the pages (~70% of our users are on mobile devices)
II. What makes a photo look good on a screen?
Let’s tackle my first issue: I want my photos to look sharp and beautiful, even on a full-screen carousel on a retina laptop. What makes an image look sharp? Why can I see a big difference in the quality of image displayed between a regular desktop screen, and a retina screen? Let me walk you through it.
1. Sharp Images
First, if we want to display images that look good to the user’s eye, we need to understand what makes an image look sharp and clear, not pixelated.
There are 2 different types of images you can use on your website:
- raster images that consist of a grid or matrix of pixels. Each pixel is basically a dot of one color. Photos are raster images.
- vector images, made up of points, lines and curves, that can be scaled up or down without losing information. They will never look blurry or pixelated (since they’re not made up of pixels), and are suitable for icons, logos and small graphics. An svg icon is a vector graphic.
A raster image looks sharp if there are enough pixels in the image to match the pixels of the physical screen.
If you don’t have enough pixels in your image, the result won’t be really good. The data of one pixel from the photo will have to take up the space of several pixels on the screen, and the result will look pixelated.
Example: For a 1280px wide desktop screen, this is what a 400px wide image looks like when it takes the whole width of the page.
Same desktop with an image width of 1280px: this time, the image looks great (of course, you need a photo that originally looks good and sharp).
On the contrary, if your image is way too large, it will still look good. If the image above was 2500 pixels wide, it would also look sharp, but not better than the exact size of 1280px. But it will be much heavier and longer to load than an image with an appropriate size.
2. Now what about retina screens?
In 2010, Apple released its first product with “retina” display, the iPhone 4. Since then, many displays have a high resolution screen or HiDPI (called retina if it’s from Apple). It means that for one CSS pixel (also called logical pixel), there are several physical pixels. With a higher density of physical pixels, the eye makes less difference between the pixels and therefore the screen looks smoother. The device pixel ratio or dpr is the ratio of the CSS pixels to the physical pixels.
If we take the example of the first retina screen, it has a dpr of 2. It means that 1 logical pixel was equivalent to 4 physical pixels (2 in each dimension).
Both iPhone 3 and 4 have the same CSS screen dimension of 320x480. The CSS max-width of the screen will be 320 in both cases, but the iPhone 4 with retina screen will actually be 640 physical pixels wide.
There’s a variety of dpr. Laptops can have a dpr of 2, desktops only 1 or 1.5 sometimes…Nowadays most smartphones have a dpr of more than one: 1.3, 2, 3… You can see here the physical and CSS dimensions of different smartphones (the last Samsung even have a dpr of 4!)
You can check the dpr in your console by using window.devicePixelRatio.
If you want to display a photo that looks sharp on a retina or HiDPI screen, it’s exactly what we said in the former section: you need at least as many pixels in the photo as in the physical screen.
So if you want to display a photo that will take 300x200 CSS pixels, you’ll actually need:
- an image of 300x200px for a desktop with dpr 1
- 600x400 for a macbook with dpr 2
- 900x600px for an iPhone X with dpr 3
- 750x500px for an Android with dpr 2.5
- etc.
I did a few tests and decided that a photo with dpr 2 was looking just fine on a device with a dpr of 3. So I decided to simplify and only use one image of dpr 1 and one of dpr 2 to cover all devices and all screen resolutions.
It really makes a difference to handle high resolution images for high resolution devices. You can see for yourself: check if your screen has a dpr of 2 or more, and if so, take a look at our website with a DPR of 1.
To do so: on Chrome, inspect the page, toggle the mobile toolbar, make sure you display the device pixel ratio and set it to 1. Don’t forget to reload the page. And then compare what you see with dpr 2 on another tab.
I personally think our images look really better now that we have “retina images”. It gives a better overall impression, and you can see the difference on details and textures, such as trees and roofs on the image above. But some people don’t really see a big difference, it’s up to you…
Okay great, on my retina macbook that has a viewport of 1440px wide, I need a photo that is 1440*2 = 2880px wide to display a full-screen image. But on my iPhone, if I were to display the same full-screen image, I would only need 375*2 = 750px. I can’t load a super heavy 2880px-wide image on iPhone if I only need 750px. So how can I manage different images?
III. Handle different images that adapt to different devices: responsive images
I’m trying to have a right image for both mobile and desktop, though the characteristics are pretty different. That’s because we use a responsive design, so I’ll talk about that to help me define what are responsive images.
Then we’ll see the two main use cases of responsive images in layouts.
1. Responsive design require responsive images
Let’s take the example of the photo in the mockups below. It’s still the product page, with a new design where the hero image takes more space than the first I showed. The first mockup of this article was also responsive, but here we can see better how much the image can change.
Our design is mobile-first and responsive: the layout changes depending on the viewport width. We want to make sure our website looks good and has a friendly user experience on any device.
We have defined a few breakpoints, to simplify I will talk about mobile (>320px), tablet (>768px) and desktop (>1280px). The layout changes at each breakpoint, as you can see on the mockups.
And between the breakpoints, the layout stretches to adapt to the viewport width. Therefore, a fullscreen image in mobile starts at 320px wide, but can be up to 767px, depending on the viewport.
With the mockup above, we are going to need different images, either in terms of image width, when we want a small image on mobile and a bigger one that looks great on high resolution desktop. Or in terms of dimensions, when the layout varies and requires different aspect ratio (width/height) each time. We could also imagine displaying an image at different zoom levels. That’s a responsive image: providing the browser different versions of an image in order to adapt to different situations.
2. The two main scenarios for responsive images
There are two main ways in which your images can change across devices: they’re called resolution switching and art direction. It’s important to understand the difference, because it will result in two different ways to handle responsive images technically, as we’ll see in part IV.
Resolution switching
That’s when you want to display the same image with the same proportions, but you want it to have the appropriate size for the device.
For example, in our wishlist page, we always want to display an image of the house with a ratio width/height of 1.5.
But depending on the breakpoint, the width of the image won’t be the same in CSS pixels.
In addition, depending on the dpr, the density of pixels of the image needs to change as we saw earlier.
For resolution switching, we want to be able to change the image density of pixels depending on the viewport and screen of the device, to load an image with the appropriate size.
Art direction
Art direction is when you change the aspect ratio or crop the image depending on the device.
I only saw this with examples of pictures of people, where you would see the entire body in desktop and only the face on mobile. So it didn’t occur to me at first that we had that on our website, since we don’t crop images to get only a zoomed version on mobile and a full version on desktop, with a very different scale. We do however change the aspect ratio of some of our images.
One typical example on our website is this one :
This is the initial image:
In our design, this photo is always cropped, but in different dimensions on mobile, tablet and desktop.
It’s possible to handle the crop in CSS, using object-fit: cover
, however it means loading images with many pixels that will never be displayed and thus are not necessary.
You could also imagine displaying different images altogether depending on the device, if you want to embed text on your image for example:
Therefore with art direction, we want to be able to make substantial changes to the image depending on the device.
Okay! So now, we know what makes an image look sharp on high resolution screen, we’ve learnt that there are two main use cases of responsive images called resolution switching and art direction. Let’s finally get to the implementation of all those things in your website!
IV. Responsive images in your HTML
The 2 useful tools in HTML to handle responsive images are the srcset attribute and the <picture> element.
I found many simple examples to get the gist of it in various articles. However, it took me some time to understand how it really worked, what image the browser would select, and what approach to choose for my use cases. I also completely misunderstood the picture tag, and decided not to use it for the wrong reasons.
Therefore, I’m going to use examples that are hopefully easy to understand, with detailed explanation of which image the browser will choose in different scenarios. This is the part where you need to focus! But don’t worry there’s a graphic summary right after.
1. srcset
As the name suggests, scrcset is an html attribute that enables you to provide a set of different image sources (instead of only one source with src), and indications so that the browser chooses the best option for the device from this set.
There are two ways to describe an image from the set, called descriptors:
- by width
- by pixel density
The srcset is a string of image urls, each associated with its descriptor, and separated by commas. We’ll see examples in the following sections.
Pixel density descriptor
In this case, you indicate the density of pixels of each image in your list. The descriptor is the dpr as a floating-point value followed by the lowercase letter “x” (1x, 1.5x, 3x…)
For example, srcset="image.jpg 1x, image-2x.jpg 2x"
The browser will detect the device pixel ratio and choose the corresponding image.
Now you might wonder how to create an image for a specific dpr? Remember that dpr is the ratio of CSS pixels to physical pixels.
So basically, an image suitable for a dpr of n has dimensions of n * width and n * height of the CSS dimensions you want to display.
Therefore the 2x image in the srcset example above has twice the width and twice the height of the 1x image. And the 1x image has the same size as the CSS size of the element. Simple!
Let’s take an example. You want to display an image in your page that will take 1200x550 CSS pixels. This image needs to look nice on a desktop with a dpr of 1 and on a HiDPI or retina laptop with a dpr of 2.
For that, you need 2 images:
image.jpg
for a dpr 1 (dimensions 1200x550px)- and
imageretina.jpg
for a dpr of 2 (with doubled width and height so 2400x1100px)
You would write the srcset like this:
In our example:
- if the dpr is 1, the browser will choose
image.jpg
- if the dpr is 1.5, it will choose
imageretina.jpg
, becauseimage.jpg
is too small. The ideal dimensions would have been 1.5*1200x1.5*550 ie 1800x825px - if the dpr is 2, it will choose
imageretina.jpg
- if the dpr is more than 2, it will take
imageretina.jpg
as well
And there you go!
Srcset with pixel density descriptor is useful when you know the exact dimensions of your image and just need to handle different resolutions.
Width descriptor
In your set of images, you indicate the browser the width of each image. The descriptor of an image is its width in pixels followed by the lowercase letter “w”.
For example, srcset=“image200.jpg 200w, image400.jpg 400w, image600.jpg 600w”
But the browser also needs to know the width that the image should take on the page, to decide which image to choose from the list. For that, you have to use the sizes attribute.
It can be a simple CSS length., e.g. sizes=”200px”
or sizes=”90vw”
sizes can also be described as a source size list: a list of sizes with a media condition described with the syntax of media queries. If the media condition is not specified, it will be the default value. e.g. sizes="(min-width: 768px) 50vw , (min-width: 1280px) 30vw , 100vw"
Therefore, if we want to display an image that will be:
- 300px wide on mobile
- 600px wide on tablet
- 900px wide on desktop
And we have 3 source images of 300px, 600px and 900px wide:
We can describe it by the following element:
Srcset with a width descriptor is very useful when you don’t know the exact dimensions of your image in your layout. Since the layout stretches to match the viewport width, in some cases your image will depends on the viewport width. Therefore, providing different sizes enable the browser to choose one that fits best the viewport.
Mixing up width and pixel density
When you read the previous example, did you wonder what image the browser would pick for a high resolution screen? Because I still want sharp images on retina screens!
The browser will pick the right image for the device from the srcset while taking into account the dpr. As mentioned in the paragraph about pixel density srcset, the ideal image width is the width displayed on the screen (known from the sizes attribute) multiplied by the pixel density of the screen. The browser chooses the image that meets the minimum required width, or a larger one. If there’s no image wide enough, it takes the widest one.
So if we take a second look at our previous example:
- On a smartphone with dpr 1, the browser chooses
image300.jpg
- On a smartphone with dpr 1.5, the minimum width needed is 300 * 1.5 = 450. Since no image with width 450px is provided, the browser picks a larger one:
image600.jpg
- On a smartphone with dpr 2, it chooses
image600.jpg
- On a tablet with dpr 1, it chooses
image600.jpg
- On a tablet with dpr 2, it chooses
image900.jpg
(but the ideal would be an image with a width of 1200px) - On a desktop, it chooses
image900.jpg
. And if it's a high resolution screen it won't look great, because there's no suitable image.
It took me quite some time to figure that out, so I hope it’s clear for you!
Tip: If you want to make sure what image the browser picks, right click on the image and open it in a new tab. The url is the one that was chosen from the srcset. Beware that the browser might also make different choices if there’s zoom, if the network is bad, etc.
⚠️ If you change the breakpoint with the device toolbar devtools, don’t forget to reload the page, to let the browser pick the image in the right conditions.
Okay I have one last trick question to check if you understood srcset. Still working with the same 3 images, what srcset
would you write if you wanted to display an image that’s always 300px wide, across all breakpoints?
You have 2 equivalent options with density or width descriptor:
In both cases:
- On a standard resolution screen, the browser takes image300.jpg
- On a high resolution screen with dpr 2, the browser chooses image600.jpg
- On a high resolution screen with dpr 3, the browser chooses image900.jpg
I would personally use a density srcset since the width is fixed, with only dpr 1 and 2, to avoid loading very big image on very high resolution screens (dpr > 2).
As you can see, using an img
with srcset
allows the browser to choose the right size of the image for resolution switching. But how can I do when I need art direction to crop differently my image depending on the breakpoint?
2. <picture>
The <picture> element is the second important HTML tool to handle responsive images.
It contains zero or more <source> elements, and an <img> element. You can use different sources to provide different image file formats, or different images altogether with media queries. Therefore, you can be much more specific with <source> elements than with a list of sources in a single srcset
.
The picture element in itself doesn’t display anything, it chooses among the source elements what to display in the img element. You can style the <img>, provide an alt
attribute, and a fallback src
. The src
will be used if the browser doesn’t find a matching source for the situation, or doesn’t handle <picture> (see browser support).
The order of the sources matter: the browser will take the first matching source. Put the most specific source first, if applicable. The <img> should be last after the sources.
If you use the sources with the media attribute, you can specify different images for different breakpoints by defining a media query for each source. For example, media="(min-width: 768px)"
can describe the source for tablet. You can also specify the orientation with media="(orientation: landscape)"
for example, or media="(prefers-color-scheme: dark)
for a different image in dark mode, and any other media query. But remember to put the most specific queries first.
Therefore it’s what you should use for art direction, for example if you want an image cropped differently for mobile, tablet and desktop.
When you use a source element inside picture
, the src
attribute is ignored. You need to use srcset (even if you only have one image in the srcset
).
You can also use the same srcset
as I described before, with width or density descriptors. And you should if you want to take into account the dpr, or if you need images of variable width in one or many sources.
Let’s say you want a fullscreen portrait image on mobile, a fullscreen square on tablet and a 800px wide panoramic on desktop:
You can use a width srcset for mobile and portrait, that will handle both the variable width depending on the viewport, and the dpr. And use a density srcset for desktop where you know the exact size of the image no matter the viewport, to handle screen resolution.
You can also use picture when you don’t need art direction but you want to handle different image formats. The WebP format developed by Google is currently the most efficient, and thus recommended by their Lighthouse performance audit tool, but it’s not supported by all browsers.
For example, if your image can be described with a single <img> tag with srcset, you can use a picture with two sources containing that srcset, one with WebP images first and one with JPEG images after. To indicate the browser that the images are in WebP you need to use the type
attribute and the corresponding MIME type, i.e. type="image/webp"
.
And you can combine everything to handle art direction, different srcset at each breakpoint plus dark mode image, all of that in WebP and JPEG 🤪
If you think responsive images suddenly became way too complex, just know that we don’t use picture elements anywhere on the site currently. Sometimes we should, but img
with srcset
suits our needs 95% of the time, and building a modular and easy to use picture component seems quite daunting. But now you know that picture element is a tool to specify in great details what image should be displayed in various situations.
V. Summary
To wrap up everything we covered about <picture> element and srcset attribute, here are questions you can try to answer every time you need to implement an image in a responsive design, and wonder how to to choose the tag and the srcset type.
1. <img> or <picture> tag?
Does my image change proportion or zoom depending on the breakpoint?
- example: the image is cropped differently on different breakpoints
- other example: the aspect ratio varies on different breakpoints
=> This is Art direction
Art direction translates technically into a <picture> tag.
If not, then does my image only change by its size or never changes at all?
- example: the bigger the screen, the bigger the image, and we always see the same portion of the image
- other example: whatever the device, I display the exact same image at a fixed size
=> This is resolution switching
Resolution switching translates technically into an <img> tag with srcset attribute.
2. What kind of srcset: width or pixel density descriptor?
If you need a picture tag, you can choose a different srcset for each source i.e. each breakpoint you define. If you use an <img> tag, then you have one srcset for all breakpoints.
Through all the possible widths of the breakpoint I’m considering, does the image keep the same size?
- yes
=> density descriptor to handle high resolution screens
- no, it depends on the viewport
=> use a width descriptor that can handle both dpr and different widths
VI. Theory and practice
So that’s the theory. Now, it’s a bit more nuanced than that. It’s not obvious to find a balance between my two initial goals for images: high quality for aesthetics, but also performance, especially on smartphones. Also, you may not want pixel-perfect images, not be able to get them, or care that handling images remains easy.
It sure makes a huge difference to handle dpr and have retina images on high resolution screens. But for performance reasons you may not need to have extra quality, if the human eye can’t see the difference.
For example, on a smartphone with dpr 3, if you have an image with dpr 1 quality it will look grainy. If you have an image of dpr 2 quality, it already looks great and sharp. If you have an image of dpr 3 quality, technically that’s what suits best the device, but I personally can’t tell the difference with dpr 2 image, and the image is heavier, longer to load. New smartphones even have a dpr of 4, so the perfect image would be much bigger than an image that already looks great to the human eye. You can read more about this on a github issue of the HTML standard.
Therefore, you may not want to have pixel perfect images all the time.
Another difficulty: sometimes, it’s not very clear whether you should use a density or width srcset, because the size changes just a little on the same breakpoint depending on the viewport. There’s no perfect solution.
For example, we use a density srcset for images with a fixed height and a width that varies just a little (+/- 10%) depending on the width of the device. We know that we’ll load a bit too many pixels or not enough for some devices. A width srcset could work too, and it would also not be pixel perfect; it’s up to you to see what’s more performant in your use case.
Also, you may want to keep the implementation of your responsive images easy. It can get quite complex to handle many picture elements with 3 srcset for mobile, tablet, desktop that each have different srcsets.
Generally speaking, the easiest way I found to implement a responsive image is to use an image tag with a width srcset. I set a fixed list of widths for all the width srcsets, and let the browser choose the best image. It works for most of our designs. Using picture and srcset density would be more specific and precise, and therefore more performant, but it’s not always easy to implement. We do use density srcset but it accounts for less than 5% of our images. We haven’t tried yet to create a picture component that is easy to use.
It’s up to you to make the compromise between the quality of images displayed for your users, the size of the image loaded, and the simplicity of implementation. I hope that the explanations about picture, srcset and how the browser chooses an image from a set, can help you figure out how you want to implement responsive images.
Conclusion
Congrats if you came this far!
I’m just presenting what I figured out about responsive images over time. I’m sure there are other possibilities and ways to see it, let me know if you have any suggestions or insights to share!
I think it’s also a great source of inspiration to go inspect other websites and check what they do for different images.
I hope you understand better the possibilities offered by HTML to handle responsive images and are now able to come up with your own way of handling them.
Sources
All photos and screenshots come from lecollectionist.com .
Illustrations: Sara Bourdon
HTML Standard:
Articles: