Responsive Images: Improving performance by letting the browser do the work

October 8, 2019

One of the most impactful ways to improve the performance on your site is by optimizing your images. Images account for 60% of the bandwidth needed to load the average webpage. The less you have to download for your images, the sooner the page is presentable and the user can begin interacting with it.

We want to serve appropriate images to all of our users, like small images on slower connections and small screens, and large, high-quality images on fast connections and high-resolution screens.

Luckily, all modern browsers support a few nifty HTML features to help us optimize our images for whatever device your users are visiting your site on.

Let the browser choose the best image

Using the features explained in this article, we can let the browser do the heavy lifting in deciding which image is the best to use for a given situation. Modern browsers are highly optimized to do this.

We provide the browser with a few key pieces of information:

  • A set of image sources and their corresponding widths via srcset
  • A list of media queries with the size of the image slot at those viewports via sizes

Combining this information with what the browser already knows:

  • Viewport size
  • Device pixel ratio
  • Currently cached images
  • Connection speed

It can then make a decision on which image to use.

The srcset attribute

The srcset attribute allows us to provide a comma-separated list of image sources with corresponding widths to an image tag. This lets the browser make a decision as to which image is the most appropriate to download.

We provide a few things for each member of a srcset:

  • The file path to the image.
  • The actual pixel width of the image
<img src=”https://dummyimage.com/1080 srcset="https://dummyimage.com/375 375w, https://dummyimage.com/500 500w, https://dummyimage.com/786 786w, https://dummyimage.com/900 900w, https://dummyimage.com/1080 1080w, https://dummyimage.com/2400 2400w" />

You’ll notice the w unit on the size—this refers to the width of the image in pixels.

Note: Keep in mind we want to continue to include a src attribute here. Browsers that don’t support srcset will default to src.

Device pixel ratio

Understanding device pixel ratio (DPR) when thinking about the image sizes in the srcset and sizes attributes is important. The device pixel ratio is the ratio of how many pixels on the hardware make up a single CSS pixel, so a 1:1 DPR screen has a single hardware pixel for every CSS pixel.

Originally retina displays were considered a 2:1 DPR, but now this descriptor has become vague as there are many different screens out there ranging from a 1:1 to a 4:1 DPR.

To name a few:

  • 1.0 - Basic desktop monitor
  • 2.0 - iPhone 8
  • 2.625 - iPhone 8 plus
  • 3.0 - iPhone X
  • 4.0 - Sony Xperia Z5

Keep in mind, the image sizes in both the src and srcset attributes are referring to CSS pixels.

Note: You can read the DPR of a device with window.devicePixelRatio.

The sizes attribute

Using the sizes attribute we can explicitly tell the browser what the slot width of the image will be at specific media condition.

Let’s go into what the sizes attribute looks like:

<img src=”https://dummyimage.com/1080 sizes="(min-width: 769px) 25vw, (min-width: 376px) 50vw, 100vw" srcset="https://dummyimage.com/375 375w, https://dummyimage.com/500 500w, https://dummyimage.com/786 786w, https://dummyimage.com/900 900w, https://dummyimage.com/1080 1080w, https://dummyimage.com/2400 2400w" />

The sizes attribute also takes a comma-separated list consisting of the media condition and what size the image slot is in either an absolute width (px or em) or a relative width (vw). Note that you can not use percentages for the slot width. The media condition provided can use and, or, and not logical operators as well, but doesn’t allow media types like screen or print. These media conditions are evaluated from top to bottom and the browser will choose the slot width of the first condition that evaluates to true.

The sizes attribute above is saying a few things:

  • At a viewport width greater than or equal to 769px the image slot is 25vw
  • At a viewport width greater than or equal to 376px and less than 769px the image slot is 50vw
  • At any other viewport width that does not meet the above media conditions the image slot is 100vw

Note: If using React, make sure to place sizes before srcset when listing the attributes. Because React uses Element.setAttribute(), adding srcset before sizes can cause certain browsers to download two images. In our work, we specifically saw this behavior on Safari.

Putting it all together

To see the real world implications of how these attributes might help us improve our site’s performance and create a better user experience, let’s take a look at a hypothetical example.

See the codepen here: https://codepen.io/formidablelabs/full/MWWYoLd

First, let’s make some assumptions:

  • Our user is on a 2.0 DPR screen
  • Our user is on a fast connection
  • The browser has no images cached

Our example is a layout that has four columns on screens larger than 768px, two columns on screens that are 376px-786px, and a single column on screens 375px and smaller.

Desktop Layout

Tablet Layout

Mobile Layout

With this layout, we can take a look at how these attributes affect what image is downloaded.

Let’s start with a just a regular image tag with a src:

<img src="https://dummyimage.com/1600" />
Just a src, no sizes or srcset:
Device widthImage slot widthImage downloaded
1920px473px1600px
768px368px1600px
375px304px1600px

As expected, the 1600px image will be downloaded at every device width. This image is more than double the image size at each of these device widths—way too big.

Now let's take a look at an image with a src and a srcset property:

<img src="https://dummyimage.com/1600" srcset="https://dummyimage.com/375 375w, https://dummyimage.com/600 600w, https://dummyimage.com/786 786w, https://dummyimage.com/1080 1080w, https://dummyimage.com/1600 1600w, https://dummyimage.com/2400 2400w" />
src and srcset, no sizes:
Device widthImage slot widthImage downloaded
1920px473px2400px
768px368px1600px
375px304px786px

This time on the two larger screens we get an even larger image than using just a src. But, isn't srcset supposed to help us download more efficient images? Well, it does... sometimes. The reason the browser downloaded these images is because, without a sizes attribute, the browser assumes that the image slot is 100vw.

Let's look at what happened on the tablet-sized device a little closer. The browser knew that the device width was 786px wide at a 2.0 DPR. It assumed the image was 100vw or 786px so it took that and multiplied it by 2.0 (DPR), took a look at the srcset and found the best-suited image.

786 * 2 = 1572

The browser slightly upscaled the image and decided the 1600px image was the most appropriate to download.

You should use sizes in tandem with srcset to get the most optimized images, whenever your image slot width deviates from 100vw at any device width.

Let's see how we can use sizes to get the best image for the job, take this image:

<img src="https://dummyimage.com/1600" sizes="(min-width: 769px) 25vw, (min-width: 376px) 50vw, 100vw" srcset="https://dummyimage.com/375 375w, https://dummyimage.com/600 600w, https://dummyimage.com/786 786w, https://dummyimage.com/1080 1080w, https://dummyimage.com/1600 1600w, https://dummyimage.com/2400 2400w" />

The sizes on this image describe our layout:

  • On a screen > 768px our image is 25vw
  • On a screen > 376px and < 769px our image is 50vw
  • On any other screen that doesn't meet the conditions above, our image is 100vw

Note: The reason we've decided to use vw instead of px to describe the image slot in sizes is because we're using css grid and the px width of the slot will fluctuate depending on the device, and there is a small amount of gap between our grid. Using vw will give the browser a good estimate to work with.

src, srcset, and sizes:
Device widthImage slot widthImage downloaded
1920px473px1080px
768px368px786px
375px304px786px

Since the browser now knows what width the image slot is, it can use that to calculate the image to download instead of the entire viewport width.

On the largest screen:

  • The viewport width is 1920px and 25vw (what was described in the sizes media condition for this device width) at that viewport width is 480px.

480 * 2 = 960

The browser upscales to the most appropriate image and decides the 1080px image is a good fit. This is over 2x smaller width than the image downloaded using srcset alone!

On the tablet:

  • The viewport width is 768px and 50vw at that viewport is 384px.

384 * 2 = 768

The browser downloaded the 786px image from the srcset—nearly spot on!

On mobile:

  • The viewport width is 375px and 100vw is well, 375px

375 * 2 = 750

This size meets the default media condition in sizes of 100vw, the browser knows the image is full width on mobile devices, so it downloads the 786px image. This is over 2x smaller width than the image we downloaded using just a src.

Gains 💪

Now, with these dummy images the actual download size benefits are pretty small because they're kinda boring (no offense to the crew at dummy image—this API is great!). Let's take a look at this very good boy to see some image size gains.

Take the example above where the user was on a mobile device where a srcset and sizes attribute was added to the image and compare it to just using a src from the first example.

The photo above is 786px wide, the size downloaded using srcset and sizes. The size of this photo is 80.1kb. The 1600px version of this photo, the image width downloaded using just src, is 254kb! Using srcset and sizes saved us more than 3x the bandwidth for that photo!

Conclusion

With a single photo, this difference isn't too noticeable, but if you're using a lot of photos on your site these differences can add up quickly and drastically improve your load times. Using these attributes can help your website be more accessible to users on mobile and on slow connections, while simultaneously delivering the highest quality image to users on high resolution screens and fast connections.

Big thanks to my coworkers Amy Dickson, Chris Bolin, and Steven Musumeche for reviewing this article.

Related Posts

What the Hex?

October 24, 2022
If you’re a designer or frontend developer, chances are you’ve happened upon hex color codes (such as `#ff6d91`). Have you ever wondered what the hex you’re looking at when working with hex color codes? In this post we’re going to break down these hex color codes and how they relate to RGB colors.

Screen and Webcam Mixing and Recording with Web APIs

September 22, 2022
There are great native applications on the market for screen recording and editing. While tools like these include a whole host of powerful editing features, for short-form content that doesn’t require post-processing, they might be overkill. I wanted to explore how far browser technology has come in the way of screen sharing and recording, and attempt to create a tool that would allow me to quickly create short-form technical video content with a little bit of flare.

Theming a React Application with Vanilla Extract

December 1, 2021
In this blog post, we're going to look at theming a React application with Vanilla Extract, which solves a lot of our theming problems in a single CSS-in-JS library.