EXPEDIA GROUP TECHNOLOGY — SOFTWARE

Images on the Web: Part 2 — Implementing responsive images

Optimize your image performance and quality using HTML and CSS

Jorge Barco
Expedia Group Technology

--

Photo by Jessica Ruscello on Unsplash

As we saw in the previous entry Images on the Web: Part 1 — Responsive Images, responsive images try to solve three kinds of problems:

  • Resolution switching: We let the browser to select what is the most suitable image to display for regular or Retina displays.
  • Art direction: The intention of the image might change when the main elements in the picture get cropped or partially removed.
  • Bandwidth usage: Use the best possible image with the smallest size.

In this article we will cover how to work with responsive images with HTML and CSS.

To make images responsive, we can use two different HTML tags: <img> and <picture>. When to use one or the other depends on the next table:

In the next sections we are going to see how to use these two tags to render the next 2.1MB image efficiently:

Original image has a size of 2.1MB. It will be used to compare the potential savings when using the techniques described in this article.
Original image (2.1 MB)

As the image doesn’t need to be that big when displayed in other devices, we will use our CDN capabilities to crop it to specific sizes (based on our breakpoints) using query params, reducing the returned image size.

The next table articulates the potential savings when using the appropriate images. It contains the different resolutions, sizes and required download time for the different croppings:

1. The <img> tag

Usage
When the images in all desired resolutions have the same size and/or same aspect ratio and all the images have the same content.

👍 Pros

  • The browser takes care of the best image to use.
  • It takes screen resolution into account.
  • The browser might take in consideration other related aspects, such as network speed.
  • Small markup.

👎 Cons

  • We are not in control of what the browser will choose.
  • If we don’t design this carefully, the browser might pick up the biggest image all the time for Retina displays.
  • We need to set up the intrinsic sizes to increase performance.

With the<img> tag, we let the browser decide which image to fetch with the same size and/or aspect ratio. To render the image, we just use src attribute as we normally do:

<img
src="https://a.cdn-hotels.com/cos/heroimage/homepage/USA__189849432.jpg"
/>

In order to declare different images with different sizes, we need to use the srcset and sizes attributes. It is important to provide a fallback image using the src attribute just in case srcset and sizes are not supported by the browser.

The srcset attribute

Using the srcset attribute, we declare different images separated by commas. We need to specify the image source and its intrinsic width separated by a space for every image. The intrinsic width is specified as the width in pixels, but instead of the ‘px’ suffix, a ‘w’ character is used. Let’s see an example:

<imgsrcset="
https://a.cdn-hotels.com/cos/heroimage/homepage/USA__189849432.jpg?impolicy=fcrop&w=375&h=486 375w,
https://a.cdn-hotels.com/cos/heroimage/homepage/USA__189849432.jpg?impolicy=fcrop&w=768&h=486 768w,https://a.cdn-hotels.com/cos/heroimage/homepage/USA__189849432.jpg?impolicy=fcrop&w=1024&h=486 1024w
"
src="https://a.cdn-hotels.com/cos/heroimage/homepage/USA__189849432.jpg"/>

The sizes attribute

The browser needs to know the size of the area where the image has to be rendered in order to pick up the most suitable option. If the sizes attribute is not provided, the browser will pick up the width of the screen, that is 100vw.

When we already know the rendering sizes and they change based on breakpoints, we can use media queries in the sizes attribute to declare what sizes are used for what breakpoints. Sizes are also separated by commas, and we can use the last item as a default value:

sizes="(min-width: 768px) 33vw, 100vw"

If we are working with components that don’t stretch and we know the maximum width of the images for each breakpoint, we could use size values in pixels.

There are some drawbacks to using the sizes attribute, as you might tie the markup to styling details too much; however, it depends on the working scenario and the potential savings.

Browser calculations

Let’s imagine we have a 400px wide device and that we use srcset to declare 3 images like in the next example:

<imgsrcset="
https://a.cdn-hotels.com/cos/heroimage/homepage/USA__189849432.jpg?impolicy=fcrop&w=375&h=486 375w,
https://a.cdn-hotels.com/cos/heroimage/homepage/USA__189849432.jpg?impolicy=fcrop&w=768&h=486 768w,https://a.cdn-hotels.com/cos/heroimage/homepage/USA__189849432.jpg?impolicy=fcrop&w=1024&h=486 1024w
"
src="https://a.cdn-hotels.com/cos/heroimage/homepage/USA__189849432.jpg"/>

The browser determines the best image to render based in the next formula:

image_width / intrinsic_width = resolution needed

Because we did not provide a sizes attribute, the browser will pick up, as we previously explained, a rendering width of 100vw. That size is equivalent to 400px in our scenario. Following this, these are the calculations done by the browser in order to select an image:

  • 375 / 400 = 0.9375
  • 768 / 400 = 1.92
  • 1024 / 400 = 2.56

For a regular 1x display, the browser will pick up the image with the closest resolution but not less than 1 (browsers might allow some flexibility on this).

In case we have a 2x display, the browser will select the image with the closest resolution but not less than 2 (browsers might allow some flexibility on this).

In our previous example, the browser might ignore the 375px image as it is smaller than the intrinsic size. The 768px wide image might be a bit overkill, but it is the best option in terms of quality as it has a resolution of 1.92.

For 2x displays (retina), 1.92x resolution is smaller than 2x, so it might pick up the 1024px wide image.

Please note we are using “might” intentionally as it is the browser’s final decision to do it or not is based on other factors, such as network speed.

The next Codepen contains an image with a srcset attribute. If you inspect the Network tab on your browser, you can see how the browser fetches one image or another based on the window width. Once the big image is downloaded, some browsers won’t request any smaller image as it can resize the big one with no quality impact. Try to change the width of the window to see how different images get fetched.

Same image size, multiple resolutions

If the image size never changes, we can provide specific images for specific resolutions using resolution descriptors in srcset instead of widths.

This is usually a good case for company rasterized logos in the header component or rasterized icons.

<imgsrcset="
https://a.cdn-hotels.com/cos/heroimage/homepage/USA__189849432.jpg?impolicy=fcrop&w=375&h=486 1x,
https://a.cdn-hotels.com/cos/heroimage/homepage/USA__189849432.jpg?impolicy=fcrop&w=750&h=972 2x,
"
src="https://a.cdn-hotels.com/cos/heroimage/homepage/USA__189849432.jpg"/>

2. <picture> tag

Usage
When images have different aspect ratios and/or the image content changes based on the render size.

👍 Pros

  • It allows control of which image to display for each scenario.
  • It allows declaration of different supported image formats.
  • We get the control of what we want to display.

👎 Cons

  • No image optimization based on network speed.
  • Verbose markup.
  • Manual mode. We don’t let the browser choose the best option.

We can use <picture> when images have different aspect ratios based on the component width.

<picture> is just a wrapper with <source> tags inside and an <img> tag used as a fallback for browsers that don’t support <picture>.

Every <source> tag can have, amongst others, the following two attributes:

  • media: to let the browser know which image to pick up depending on the breakpoints.
  • srcset: to set the image associated to the source tag. We use <picture> scrset attribute in the same way we use <img> srcset . The source selected will depend on the media query.
<picture>
<source
srcset="image_small_375.jpg 375w,image_small_768.jpg 768w"
media="(max-width: 375px)"
/>
<source
srcset="image_large_1024.jpg 1024w,image_large_2048.jpg 2048w"
media="(min-width: 769px)"
/>
<img
src="image_large.jpg"
/>
</picture>

We can follow the same convention as explained in the previous sections to declare multiple images for a scenario based on resolution descriptors.

<picture>
<source
srcset="image_small_375.jpg 1x,image_small_768.jpg 2x"
media="(max-width: 375px)"
/>
<source
srcset="image_large_1x.jpg 1x,image_large_2x.jpg 2x"
media="(min-width: 769px)"
/>
<img
src="image_large_1x.jpg"
/>
</picture>

⚠️ Picture tag overuse

You can see lots of examples on websites overusing the tag <picture> when they should be using <img> instead. <img> lets the browser optimize which image to use. <picture> should be only used when we really want to take control of what to render, especially when facing art direction problems.

3. Responsive CSS background images

In the previous examples we have been working with <img> and <picture> tags. However, sometimes images are loaded using the background-image CSS property. If that is the case, you can make your images responsive by using image-set.

image-set is a feature that is not fully supported by all browsers at the moment of writing, but as you can see on https://caniuse.com/css-image-set, most of them support at least url() and x resolution descriptors.

That means that we can use image-set in most of the scenarios we work on. Chrome needs the -webkit vendor prefix to enable this feature at the time of writing.

It is a good idea to provide a fallback image using background-image just in case image-set is not supported by the browser:

background-image: url(image_small_375.jpg);background-image: -webkit-image-set(
url(image_small_375.jpg) 375w,
url(image_small_768.jpg) 768w
);
background-image: image-set(
url(image_small_375.jpg) 375w,
url(image_small_768.jpg) 768w
);

Resolution switching

As shown in the previous sections, images can be defined using resolution descriptors as in the example below:

background-image: image-set(
url(image1x.jpg) 1x,
url(image2x.jpg) 2x
);

Art direction

We can set up different background images using media queries:

.example {
background-image: url(mobile.jpg);
}
@media (min-width: 376px) {
.example {
background-image: url(desktop.jpg)
}
}

Combining resolution switching and art direction

We can mix up both scenarios to support art direction and resolution switching by combining media queries and image-set:

.example {
background-image: url(mobile.jpg);
}
@media (min-width: 376px) {
.example {
background-image: image-set(
url(desktop.jpg) 1x,
url(desktopx2.jpg) 2x
);
}
}

You can check the next Codepen and see how the browser fetches different images based on the breakpoints we declare and the display resolution. Try to change the width of the window to see how different images get fetched.

Getting control of screen resolution for background-images

So far we have seen how to combine resolution switching and art direction. When we use resolution descriptors, the browser will decide what image to use. If we want to take control of what image to use, we can combine media queries and min-device-pixel-ratio (or min-resolution if we want to use dpi) as we show in the next example:

@media (min-resolution: 2dppx), (-webkit-min-device-pixel-ratio: 2)

Conclusion

In this blog post we have described how to use <img> and <picture> tags depending on whether the image aspect ratio or the image content changes based on different breakpoints. Moreover, we have discussed how to use resolution descriptors to request images that look sharp in both regular and retina displays.

Lastly, we have covered how to implement resolution switching and art direction using CSS background-image and image-set properties and how to get control of the image rendered using min-device-pixel-ratio.

References

--

--