Srcset

This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the web-development category.

Last Updated: 2024-04-25

When an image is loaded with only a regular src attribute the browser doesn’t know how wide it is until after it’s loaded. But with the srcset attribute we can tell the browser how wide each of our images is in advance. It can then use that information to load the most appropriate image depending on the size of the viewport at the time.

The old-fashioned src attribute provides the default here... and is also a fallback for older browsers Load the smallest image as default in src. Then add your default image and its larger alternative images inside the srcset attribute as a comma separated list, specifying the width of each, (after a space), with [width]w:

<img src="image-small.png"
    srcset="image-small.png 320w, image-medium.png 800w, image-large.png 1200w"
    alt="Image description">

Note that you cannot use heights here in srcsets.. just widths.

It’s important to include your default image (the one passed to src) in the srcset along with its width

If the browser doesn’t support srcset, (something that’s only an issue for quite old browsers), it will fall back to showing the small image. If catering for older browsers is necessary for your projects, be sure to include some CSS to scale your default image to the correct size.

Sizes Attribute

Even though the above works, you theoretically need sizes attribute too according to the srcset spec. (If you leave it out, the default value wil lbe 100vw, 100% of your device viewport.)

The sizes attribute allows you to specify a width for the image layout. Note this is a separate concept to the real image widths specified per file in the srcset attribute. The width given in sizes is related solely to layout, and can be thought of as creating empty placeholder slots into which the browser can insert images from your srcset. It doesn’t matter which image file the browser chooses to load from your srcset, it will display it at the width you specified in sizes.

This is always 80%, no matter which of the 3 images was chosen

<img src="image-small.png"
    srcset="image-small.png 320w, image-medium.png 800w, image-large.png 1200w"
    sizes="80vw"
    alt="Image description">

If you put media conditions in your sizes attribute instead, then you can conditionally change your image layout based on the viewport properties. Media conditions are the true or false states we evaluate when we use media queries

  <img src="image-small.png"
    srcset="image-small.png 320w, image-medium.png 800w, image-large.png 1200w"
    sizes="(min-width: 60rem) 80vw"
    alt="Image description">

Now the image will only be sized at 80vw if the viewport is at least 60rem wide.

Browser Calculations

The browser will generally choose the smallest image from the srcset that is still wider than the current "slot" (from sizes)

Picture tag

The picture element is another layer more complex and also gives the browser the ability to choose image formats. But generally avoid this added complexity if you just need responsive images.

The first thing we’ll do is specify that our current set of images should only be used when the viewport is in landscape orientation. In our <source> element we can do this by adding a media attribute with the value (orientation: landscape). Note that this in addition to the sizes bit.

<picture>
    <source
        media="(orientation: landscape)"
        srcset="image-small.png 320w,
image-medium.png 800w,
image-large.png 1200w"
        sizes="(min-width: 60rem) 80vw,
(min-width: 40rem) 90vw,
100vw">
    <img src="image-small.png" alt="Image description">
</picture>

The sizes attribute is specifically used to create a collection of layout sizes, with a layout width specified after each condition. The media attribute contains just a media condition, and only if it evaluates to true does the <source> element it’s attached to activate.

When constructing your <picture> elements, be mindful of the fact that as soon as the browser hits a <source> element with a media attribute that returns true it will stop looking and render from the srcset for that element. So make sure you put media queries with higher specificity first. Another way of looking at this: if min-width:0 is first and min-width 500 second, the browser will pick up the smaller image (min-width:0) first and stop. Therefore you should instead put the higher widths as earlier sources.

The fact that <picture> will go through a stack of <source> elements until it finds an image it can load successfully means you can use it quite handily to load newer file formats that don’t have 100% browser support yet.

<picture>
  <source type="image/webp" srcset="illustration.webp">
  <source type="image/svg+xml" srcset="illustration.svg">
  <img src="illustration.png" alt="A hand-made illustration">
</picture>

Retina (etc.)

As well as specifying width in the srcset, you can also specify pixel density

For example:

<img src="/photo.jpg" srcset="/photo@1000w.jpg 1000w, /photo@2x.jpg 2x, /photo@3x.jpg 3x" alt="" />

The basic image is 1000w so it will be used when your 100vw (in the implicit but absent sizes attribute) is equal to those 1000w. Now, the 2x will be used if the device has double the pixel density of a regular 1x device.

A more advanced use of this is with the the picture tag

<picture>
  <!-- Put the biggest at the top, because the first appropriate one will be selected -->
  <source media="(min-width: 60em)" srcset="large-@2x.jpg 2x, large.jpg 1x">
  <source media="(min-width: 38em)" srcset="medium-@2x.jpg 2x, medium.jpg 1x">
  <img src="small.jpg" alt="A giant squid swimming deep in the sea">
</picture>

Reasoning for why this srcset complexity is needed

Why can't we do this with JS or CSS? When the browser starts to load a page, it starts to download (preload) any images before the main parser has started to load and interpret the page's CSS and JavaScript. That mechanism is useful in general for reducing page load times, but it is not helpful for responsive images — hence the need to implement solutions like srcset.

How does the browser choose the image? It caclulates the best bit base on width. For non-retain, the ratio it is looking for is 1 (retina: 2). So if your device width is 1000px and you have images 500, 1250, and 2500px, it will choose the smallest image that is still bigger than the width. This is 1250px here. If retina, it will go for 2500px.

CSS Option

The image-set in CSS is the equivalent of the srcset in HTML.

// Fallback for browsers that do not support image-set
background-image: url(/images/image-md_2x.jpg);

background-image: image-set(
  url(/images/image-lg_1x.jpg) 1x,
  url(/images/image-lg_2x.jpg) 2x  );

Resources