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"
>
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;
}
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.
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:
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"));
}
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"
>
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);
}
...becomes...
.box {
background-image: image-set(
url("small-landscape-750x536.jpg") 1x,
url("large-landscape-2048x1536.jpg") 2x);
}
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);
}
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);
}
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.
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>
Quoting my own StackOverflow answer here:
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!
Top comments (4)
Though I like the concept of "responsive" apps, putting the same image in multiple resolutions on the server does not seem very appealing to me. In most cases, the images will anyway not fit perfectly to the browser pixel resolution. So the browser will need to rescale the image again, which does not cost bandwidth, but some rendering time anyway.
By the way, resolution is not the only factor that affects the file size of an image. With JPG or PNG you can change the compession factor to get a smaller size. So, you will lose some quality, but gain much better speed. It could be a good idea to deliver an image depending on viewport size AND bandwidth.
Leaving the heavy lifting to the server could be a far better solution, even for a web app, that runs completely in the browser. Some CMS like Directus allow to use resolution parameters when the browser asks for an image. This gives you in any case the best possible quality with the smallest size.
Agreed, the server should optimize images, like a CMS handling uploaded image files and generating different optimized versions in different sizes. Then the CMS theme can use source set markup to let the browser decide which image is the most appropriate while saving unnecessary bandwidth.
I have added code examples for complex image / picture source sets and explicitly stated in my conclusion that currently I would not use background images with image-set as long as using actual image tags is an option.
Do you know in Directus WYSIWYG editor how to make image responsive?
Directus reduces the image size on demand, so it is up to your application to ask for the "right" resolution. I´m not sure this is shown in the editor, but maybe there is an option to do so?!?
My knowledge about Directus is not up-to-date, so maybe you check out directus/discussions on github.