Anna Monus Anna is a technical writer who covers frontend frameworks, web standards, accessibility, WordPress development, UX design, and more. Head to her personal blog Annalytic for more content.

Improving performance with HTML responsive images

8 min read 2398

Improving performance with HTML responsive images

When speaking about responsive images, we usually focus on CSS techniques that make an image adapt to different viewport sizes, such as setting the max-width property to 100 percent. However, using solely CSS to make images responsive won’t improve performance and page load times, as you’re still serving the same size image for all devices. For instance, loading a 2000px image on mobile comes with a huge (and unnecessary) overhead.

Luckily, HTML also has its own syntax, elements, and attributes for responsive images that let you serve different images for different viewport sizes, resolutions, and other conditions. In this guide, we’ll look into how to add responsive images in HTML and discuss the following features:

  • the <img> element
  • the srcset and sizes attributes
  • x and w descriptors
  • the <picture> and <source> elements
  • the media and type attributes

The standard image syntax

To add responsive images in HTML, your starting point is always the standard image syntax, consisting of the <img> element and the src and alt attributes:

<img src="images/myimage.jpg alt="my image">

For the src attribute, you can use either an absolute (starting with the http:// or https:// protocol) or a relative path — I’ve used the latter above.

You always need to “build” the responsive image syntax on top of this standard <img> definition; this also ensures backward compatibility.

The srcset attribute

The srcset attribute is an optional attribute of image-related HTML elements, including the <img> tag. You can use it to assign different image sources to certain features of the user’s device, such as viewport size or pixel density. The user’s browser will only load the image that’s the most suitable for the user’s device — which can mean a significant performance gain.

When using the srcset attribute, you’re supposed to add the same image in different sizes. This is because this attribute only gives hints to the user’s browser that it can still load a different image as it also considers other things, such as the bandwidth of the network. If you use srcset, the browser will assume that all of the image sources are visually identical. If you want to serve different-looking images, you’ll need to use the <picture> and <source> elements — we’ll look into them later on.

There are two ways to serve different size images with the srcset attribute:

  • using x descriptors:
    • you can specify different image sources based on the pixel density of the user’s device
    • you can serve different images for low-resolution vs. high-resolution devices
  • using w descriptors and the sizes attribute:
    • the term “w descriptor” stands for “width descriptor”
    • you can specify different image sources based on the width of the images
    • the browser considers both pixel density and layout dimensions (the space it needs to allocate for the image)

Now, let’s have a look at the respective syntaxes.

The srcset attribute with x descriptor(s)

Using the following HTML, you can serve images for both low-resolution and high-resolution displays:

We made a custom demo for .
No really. Click here to check it out.

<img src="images/myimage.jpg" srcset="images/myimage-2x.jpg 2x" alt="my image">

The second image, myimage-2x.jpg is twice as big as the default one (e.g. 1280x960px compared to 640x480px), but it will only be loaded on high-resolution screens. It will be the user’s browser that decides which image to serve, mostly based on the pixel density of the display.

Note that you are supposed to add the smaller image to the src attribute because this will be the default. For the srcset attribute, you also need to use the 2x descriptor so that the browser will know that this is the image intended for high-resolution screens.

You can add more than one value to the srcset attribute, too. For instance, with the following code, you can serve images for 4K monitors:

<img src="images/myimage.jpg" srcset="images/myimage-2x.jpg 2x, images/myimage-4x.jpg 4x" alt="my image">

To add more than one image source to srcset, you need to use the respective x descriptors (2x, 3x, 4x, etc.) and separate the value pairs by commas.

The srcset attribute with w descriptor(s) and the sizes attribute

If you want to target both the pixel density and the layout size of the user’s device, you’ll need to use the srcset attribute together with one or more width descriptors and the sizes attribute.

A w descriptor defines the width of a source image. For instance, 600w indicates that the image is 600px wide. As long as you separate them with commas, you can add as many image sources to the srcset attribute as you want, like so:

<img  src="images/myimage-small.jpg" 
    srcset="images/myimage-small.jpg 300w,
    images/myimage-medium.jpg 600w,
    images/myimage.jpg 1200w,
    images/myimage.jpg 1800w" 
    sizes="(max-width: 500px) 100vw,
    (max-width: 1000px) 90vw,
    calc(60vw - 20px)" 
    alt="my image">

If you use width descriptors, you’ll need to use the sizes attribute, too — otherwise, the browser won’t be able to find out which image source to choose. The sizes attribute is frequently misunderstood because people tend to assume that the width values used in the media conditions of the sizes attribute are related to the w values (300w, 600w, etc.) used in the srcset attribute — however, they are independent of each other.

The sizes attribute serves two purposes:

  1. informs the browser about the space it needs to allocate for the image, depending on the layout
  2. allows the browser to pick the best-fitting image using the width of the available images (defined by the w descriptors) and the pixel density of the screen:
    1. Example: for a smaller, full-HD screen — say, 13.3” — the browser can pick the same image, such as the 1200w one, as it does for a larger, low-res screen — let’s say 17.3”. The browser’s choice also depends on the size distribution of the available images and other conditions because, as I’ve mentioned above, the srcset and sizes attributes are optional but not obligatory for the browser (as opposed to the <picture> and <sources> elements)

In the above code example, I’ve defined three layouts within the sizes attribute. On viewports smaller than 500px, the image will span across 100 percent of the viewport width (100vw). On medium viewports that are smaller than 1000px, the image will need 90 percent of the viewport width (90vw). And, on viewports larger than 1000px, the browser will need to allocate 60 percent of the viewport width, minus 20px for the left and right margins (calc(60vw - 20px)). The latter is also the default value, so I haven’t used any media conditions here.

Using media conditions

You can use any number of media conditions to define different layouts and the space the image will need in each, but it is important to note that adding media conditions to the sizes attribute is optional. It only has to include one default value, and if your layout looks the same at all viewport sizes, you don’t need to use any media conditions. For instance, the above example could alternatively look like this:

<img  src="images/myimage-small.jpg" 
    srcset="images/myimage-small.jpg 300w,
    images/myimage-medium.jpg 600w,
    images/myimage.jpg 1200w,
    images/myimage.jpg 1800w" 
    alt="my image">

Beware: you’ll find some tutorials saying that you can use the x and w descriptors together, but according to the docs, these two shouldn’t be used together. This does make sense when you think about it, as when you use w descriptors, the browser also considers pixel density, too.

So, if you just want to target pixel density, use x descriptors. If you want to target both pixel density and layout, use w descriptors.

The <picture> and <source> elements

While the <img> element used with the srcset attribute and x or w descriptors is an excellent solution if you want to serve the same image in different sizes, sometimes you’ll want to load visually different images for different user agents.

There are two main use cases that cover why you might want to do that:

  • using different art directions for different media conditions, for example, zoomed-in and zoomed-out versions of the same image
  • using different image formats so that you can serve next-generation images for modern browsers

HTML has two elements for these situations: <picture> and <source>. The latter can be used to specify different media resources for the <picture>, <audio>, and <video> elements.

Now, let’s see what the syntax looks like.

Image sources with different art directions

Let’s say you have the following image:

Large flamingos sample
Photo by Summer Li from Pexels

This is a perfect image for large screens, but for medium-sized screens you might want to serve a closer shot that’s also smaller in size:

Medium sized flamingos sample, zoomed in

And on small screens, you just want to show one of the flamingos in a much smaller image:

Small, profile flamingos sample

You can define these three image sources using the following HTML code:

  <source srcset="images/flamingos-closer.jpg" media="(min-width: 768px) and (max-width: 1199px)">
  <source srcset="images/flamingos-far.jpg" media="(min-width: 1200px)">
  <img src="images/flamingo-profile-small.jpg" alt="flamingo">

As you can see above, we added the smallest, closest image as the default and defined a relevant media condition for each image source.

If you want, you can also specify more than one image within each srcset attribute using the aforementioned x or w descriptors, for example:

    <source srcset="images/flamingos-closer.jpg, images/flamingos-closer-2x.jpg 2x" media="(min-width: 768px) and (max-width: 1199px)">
  <source srcset="images/flamingos-far.jpg, images/flamingos-far-2x.jpg 2x" media="(min-width: 1200px)">
  <img src="images/flamingo-profile-small.jpg" srcset="images/flamingo-profile-small-2x.jpg 2x" alt="flamingo">

Note that with the media attribute, you can target both width and any other media features, such as orientation, aspect ratio, and more.

If you use the <picture> element with the <source> tag, the user’s browser will still load only one image — the most suitable one — but now, the media condition is not a hint or an option like before. Instead, it’s a rule that the browser has to follow in any case. In other words, the browser will assume that the image sources are visually different from each other and treat them accordingly.

Image sources in different formats

Besides media conditions, you can also serve an image in different formats. This can be especially useful if you want to use next-generation image formats, such as AVIF or WebP, that are smaller in size (which can improve performance on their own) but are not supported by older browsers.

Here, you’ll need to define the MIME type of each image source using the type attribute:

  <source srcset="images/myimage.avif" type="image/avif">
  <source srcset="images/myimage.webp" type="image/webp">
  <img src="images/myimage.jpg" alt="my image">

If you use the code above, the user’s browser will check the consecutive MIME types one by one — so, add the one that you want to have checked first (I used AVIF in the example above).

Technically, you could also use the type and media attributes together within each <source> element — however, note that this can add a lot of extra complexity:

  <source srcset="images/flamingos-closer.webp" media="(min-width: 768px) and (max-width: 1199px)" type="image/webp">
  <source srcset="images/flamingos-closer.jpg" media="(min-width: 768px) and (max-width: 1199px)" type="image/jpeg">
  <source srcset="images/flamingos-far.webp" media="(min-width: 1200px)" type="image/webp">
  <source srcset="images/flamingos-far.jpg" media="(min-width: 1200px)" type="image/jpeg">
  <source srcset="images/flamingo-profile-small.webp" type="image/webp">
  <img src="images/flamingo-profile-small.jpg" alt="flamingo">

Browser support

Browser support for the HTML syntax related to responsive images is relatively good:

  • The srcset and sizes attributes are supported by Edge 16+, Firefox 38+, Chrome 38+, and Safari 9+, but they’re not supported by any version of Internet Explorer
  • The <picture> element is supported by Edge 13+, Firefox 38+, Chrome 38+, and Safari 9.1+, but it’s not supported by any version of Internet Explorer
  • The <source> element is supported by Internet Explorer 9+, Edge 12+, Firefox 15+, Chrome 90+, and Safari 14.1+

As Chrome and Safari began to support the <source> element relatively late, it comes with the lowest level of browser support out of these four HTML features related to responsive images.

Plus, the responsive image syntax degrades gracefully. If a browser doesn’t support one or more elements or attributes, it will simply use the default image added to the very well-supported <img> element.

Wrapping up

Adding different image sources in HTML to serve the most performant image for each user agent can result in a huge performance gain, especially on mobile devices. However, a complex syntax can make your coding workflow overly complicated and your code harder to read.

Essentially, the trade-off is between complexity and performance, so you need to decide whether it’s worth the hassle for you or not. Obviously, image performance is more important on image-heavy websites, and may not be as important if you have just one or two images on the screen at any given time.

You can also automate the process. CDNs such as Netlify and Cloudflare and CMSs such as WordPress already come with built-in image optimization capabilities — they generate several versions of the same image, add the <picture> element, apply media conditions, and more. There are also open-source solutions that can help you with automation, such as the Image Responsiver or Get Sizes tools.

Finally, don’t forget that this guide only discusses how to serve responsive images in HTML. You’ll still need to use CSS to adapt the image added by HTML to the design, such as adjusting the width and max-width attributes or using a responsive image grid.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

Anna Monus Anna is a technical writer who covers frontend frameworks, web standards, accessibility, WordPress development, UX design, and more. Head to her personal blog Annalytic for more content.

Leave a Reply