Responsive background images with image-set, the srcset for background-image

ingosteinke

Ingo Steinke, web developer

Posted on January 24, 2022

Responsive background images with image-set, the srcset for background-image

Source sets can help us to make websites load faster. We can use them in different ways to offer browsers alternative versions of the same image to match screen size, pixel density, or network speed.

A Source Set for Background Images

The image-set property allows us to do the same for background images in CSS. This feature has been requested for years, but it did not get the same hype as other, newer, CSS features like parent selectors or container queries.

Understanding Image Sets step by step

First, let's make sure we understand source sets.

What are source sets and how to use them?

In a typical use case, we provide different image versions and add our recommendation for appropriate screen sizes, but it's up to the browser to decide which image to load:

The image-set() function allows an author to ignore most of these issues, simply providing multiple resolutions of an image and letting the user agent decide which is most appropriate in a given situation.

Source: csswg.org

Providing Image Files

Let's start an put one image in an image element, for example this photography of a landscape, 2048 pixels wide, and 1536 pixels high. As a high resolution photography with a lot of details, the file size is 557.7 kB, which is roughly half a megabyte.

We will use an image element to show this photograph on our website. We must specify the image source (the URL to the image file) and the original image dimensions (width and height).

<img
  src="large-landscape-2048x1536.jpg"
  width="2048"
  height="1536"
  alt="landscape"
>
Enter fullscreen mode Exit fullscreen mode

Adding the following style sheet will make browsers resize our image (and every other image on that page) proportionally when the horizontal viewport with is smaller than the original image width.

img {
  max-width: 100%;
  height: auto;
}
Enter fullscreen mode Exit fullscreen mode

We can test that it works as intented.

But what a waste of bandwidth!

This is responsive in a visual way, but even on a small old mobile phone, browsers will still load the same large image file, half a megabyte of data, only to display a shrunk version of the same image on a tiny screen.

Same large image on a small mobile phone screen

A much smaller Image File that still looks good

If our mobile screen is 326 CSS pixels wide, at a resolution of 2 device pixels per CSS pixel, we need an image of 750 x 536 pixels to fill our screen. Scaling our image down to that size and saving it as a high quality JPEG file (with JPEG quality set to 80), the new image file only takes up 90 kilobytes, and the image still looks good.

And if you're not too ambitious, so does the 70 kB file after further image processing on codepen's asset server:

large-landscape-750x536.jpg

Alternative modern image formats

Update: another use case might be webp and avif support, as suggested on MDN, combined with a legacy fallback:

.box {
  background-image: url("fallback-balloons.jpg");
  background-image: -webkit-image-set(
    url("large-balloons.avif"),
    url("large-balloons.jpg"));
  background-image: image-set(
    url("large-balloons.avif") type("image/avif"),
    url("large-balloons.jpg") type("image/jpeg"));
}
Enter fullscreen mode Exit fullscreen mode

Adding Source Sets and Sizes to our Image Elements

Now let's tell our browser to use the smaller version if the screen size is not larger than 750 (CSS) pixels. We can add a srcset attribute to our existing image.

<img
  src="large-landscape-2048x1536.jpg"
  srcset="small-landscape-750x536.jpg 750w,
          large-landscape-2048x1536.jpg 20480w"
  width="2048"
  height="1536"
  alt="landscape"
>
Enter fullscreen mode Exit fullscreen mode

For more complex definitions, we could wrap a picture element around our image and add multiple source elements each with its own srcset attribute. That can be handy if we need to combine different aspects of responsive images for the same image element, like screen width and pixel density.

Responsive Background Images

Why use background images at all? Well, in the old days before the object-fit property and before layering content using positioning and z-index worked as it did today, background images were a very useful technique to code hero banners and they still provide an easy way to add optional decoration to pages and elements.

Despite their smooth and flexible visual styling, it used to be impossible to optimize background images to save mobile bandwidth, and the warning about "very limited support" of the image-set property probably did not help to make it popular among web developers either.

Using Image Sets for Background Images

Defining an image-set for a background-image url is easy if we know how to use srcset attributes for img and source elements.

A drawback of limited image-set support in current browsers is that we can't use pixel width resolutions, so we have to set pixel density (1x, 2x) etc. as a selector instead.

We can use image-set as a replacement for a single url, so that

.landscape-background {
background-image: url(large-landscape-2048x1536.jpg);
}
Enter fullscreen mode Exit fullscreen mode

...becomes...

.box {
  background-image: image-set(
    url("small-landscape-750x536.jpg") 1x,
    url("large-landscape-2048x1536.jpg") 2x);
}
Enter fullscreen mode Exit fullscreen mode

For the sake of maximum browser compatibility, we should add a webkit-prefixed version as well as a single image url. Currently, Chrome browser still don't support the unprefixed version.

.box {
  background-image: url("small-landscape-750x536.jpg");
  background-image: -webkit-image-set(
    url("small-landscape-750x536.jpg") 1x,
    url("large-landscape-2048x1536.jpg") 2x);
  background-image: image-set(
    url("small-landscape-750x536.jpg") 1x,
    url("large-landscape-2048x1536.jpg") 2x);
}
Enter fullscreen mode Exit fullscreen mode

Progressive Enhancement with w-Units

CSS 4 Images draft already proposed to introduce width and height units in the future:

We should add "w" and "h" dimensions as a possibility to match the functionality of HTML’s picture.

While the quoted "we should add" was meant to say that browser vendors should add the functionality to their CSS engines, it could also mean that we, as web developers, should add the dimensions to our code even before any browser actually supports them.

Using progressive enhancement, which means to use new features in an optional way, we could simply add another line with a width-based image-set. It will be ignored for containing (currently) invalid values, but it will start to work once browsers start to implement the new syntax.

Last but not least, we can add a static background color which will display before the image has loaded, or in case the image fails to load for some reason.

.box {
  background: skyblue;
  background-image: url("small-landscape-750x536.jpg");
  background-image: -webkit-image-set(
    url("small-landscape-750x536.jpg") 1x,
    url("large-landscape-2048x1536.jpg") 2x);
  background-image: image-set(
    url("small-landscape-750x536.jpg") 1x,
    url("large-landscape-2048x1536.jpg") 2x);
  background-image: image-set(
    url("small-landscape-750x536.jpg") 750w,
    url("large-landscape-2048x1536.jpg") 20480w);
}
Enter fullscreen mode Exit fullscreen mode

Firefox 96 supports image-set without prefix, but still sees 750w and 2048w as invalid values, falling back to the image-set with density values.

Image description

Internet Explorer would still recognize our first line, the background image wihtout an image-set, so it looks we're all set to have a nice display in every browser, and a performance optimiziation for the progressive ones.

This is our complete demo code in action:

Conclusion and Alternatives

Due to the limited browser support, I prefer using regular <img> elements. Images inside of <picture> elements support complex adaptive source sets combining rules for width and pixel density for each image at the same time, also known as the "art direction" use case.

<picture>
  <source media="(min-width: 800px)" srcset="head.jpg, head-2x.jpg 2x">
  <source media="(min-width: 450px)" srcset="head-small.jpg, head-small-2x.jpg 2x">
  <img src="head-fb.jpg" srcset="head-fb-2x.jpg 2x" alt="a head carved out of wood">
</picture>
Enter fullscreen mode Exit fullscreen mode

Quoting my own StackOverflow answer here:

Understanding srcset and sizes in combination with HiDPI monitors

I have been into CSS for quite a while now, but srcset and sizes for the image element confuse me. Here is an example that I thought would work.

<img alt="Background image flowers"
    srcset="img/flowers-480.jpg 480w,
            img/flowers-480@2x.jpg 480w 2x,
            img/flowers-768.jpg 768w,
            img/flowers-768@2x.jpg 768w 2x,
            img/flowers-960.jpg 960w,
            img/flowers-960@2x.jpg 960w 2x,
            img/flowers-1280.jpg 1280w,

What's next in CSS?

Thanks for reading, and watch out, there is more to come!

💖 💪 🙅 🚩
ingosteinke
Ingo Steinke, web developer

Posted on January 24, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related