The CSS image-set()
function has been supported in Chromium-based browsers since 2012 and in Safari since version 6. Support recently landed in Firefox 88. Let’s dive in and see what we can and can’t do today with image-set()
.
Multiple resolutions of the same image
Here’s what the CSS spec has to say about image-set()
:
Delivering the most appropriate image resolution for a user’s device can be a difficult task. Ideally, images should be in the same resolution as the device they’re being viewed in, which can vary between users. However, other factors can factor into the decision of which image to send; for example, if the user is on a slow mobile connection, they may prefer to receive lower-res images rather than waiting for a large proper-res image to load.
It’s basically a CSS background equivalent to the HTML srcset
attribute for img
tags. By using image-set
we can provide multiple resolutions of an image and trust the browser to make the best decision about which one to use. This can be used to specify a value for three different CSS properties: content
, cursor
, and most useful of all, background-image
.
.hero {
background-image: image-set("platypus.png" 1x, "platypus-2x.png" 2x);
}
1x
is used to identify the low-res image, while 2x
is used to define the high-res image. x
is an alias of dppx
, which stands for dots per pixel unit.
Chrome/Edge/Opera/Samsung Internet currently require a -webkit-
prefix. If you’re using Autoprefixer, this will be handled automatically. Safari no longer requires the prefix but uses an older syntax that requires a url()
function to specify the image path. We could also include a regular old background-image: url()
to support any browsers that don’t support image-set
.
.hero {
/* Fallback */
background-image: url("platypus.png");
/* Chrome/Edge/Opera/Samsung, Safari will fallback to this as well */
background-image: -webkit-image-set(url("platypus.png") 1x, url("platypus-2x.png") 2x);
/* Standard use */
background-image: image-set("platypus.png" 1x, "platypus-2x.png" 2x);
}
Now users on expensive fancy devices will see a super sharp image. Performance will be improved for users on slow connections or with cheaper screens as their browser will automatically request the lower-res image. If you wanted to be sure that the high-res image was used on high-res devices, even on slow connections, you could make use of the min-resolution
media query instead of image-set
. For more on serving sharp images to high density screens, check out Jake Archibald’s recent post over on his blog.
That’s pretty cool, but what I really want is to be able to adopt the latest image formats in CSS while still catering for older browsers…
New image formats
Safari 14 shipped support for WebP. It was the final modern browser to do so which means the image format is now supported everywhere (except Internet Explorer). WebP is useful in that it can make images that are often smaller than (but of the same quality as) JPG, PNG, or GIF.
There’s also a whole bunch of even newer image formats cropping up. AVIF images are shockingly tiny. Chrome, Opera and Samsung Internet have already shipped support for AVIF. It’s already in Firefox behind a flag. This image format isn’t supported by many design tools yet but you can convert images to AVIF using the Squoosh app built by the Chrome team at Google. WebP 2, HEIF and JPEG XL might also make it into browsers eventually. This is all rather exciting, but we want browsers that don’t support these newer formats to get some images. Fortunately image-set()
has a syntax for that.
Using new image formats by specifying a type
Browser support note: The feature of image-set
that I’m about to talk about currently has pretty terrible browser support. Currently it’s only supported in Firefox 89.
HTML has supported the <picture>
element for years now.
<picture>
<source srcset="./kitten.avif" type="image/avif">
<img src="./kitten.jpg" alt="a small kitten">
</picture>
image-set
provides the CSS equivalent, allowing for the use of next-gen image formats by specifying the image’s MIME type:
.div1 {
background-image: image-set(
"kitten.avif" type("image/avif"),
"kitten.jpg" type("image/jpeg")
);
}
The next-gen image goes first while the fallback image for older browsers goes second. Only one image will be downloaded. If the browser doesn’t support AVIF it will ignore it and only download the second image you specify. If AVIF is supported, the fallback image is ignored.
In the above example we used an AVIF image and provided a JPEG as a fallback, but the fallback could be any widely supported image format. Here’s an example using a PNG.
.div2 {
background-image: image-set(
"puppy.webp" type("image/webp"),
"puppy.png" type("image/png")
);
}
In Chromium and Safari, specifying the type
is not supported yet. That means you can use image-set
today only to specify different resolutions of widely-supported image formats but not to add backwards-compatibility when using WebP or AVIF in those browsers. It should be possible to provide both multiple resolutions and multiple image formats, if you are so inclined:
.div2 {
background-image: image-set(
"puppy.webp" type("image/webp") 1x,
"puppy2x.webp" type("image/webp") 2x,
"puppy.png" type("image/png") 1x,
"puppy2x.png" type("image/png") 2x
);
}
Hopefully browser support will improve soon.
<picture>
for backgrounds instead
Using Maybe you don’t need background-image
at all. If you want to use modern image formats, you might be able to use the <picture>
element, which has better browser support. If you set the image to position: absolute
it’s easy to display other elements on top of it.
As an alternative approach to using position: absolute
, CSS grid is another easy way to overlap HTML elements.
Here’s a link to the chrome bug for unprefixing image-set(). Please vote for it to let the Chrome team know you want it: https://bugs.chromium.org/p/chromium/issues/detail?id=630597&q=css%20image-set&can=2
thanks for the link.
This was super helpful. Thanks!
Thanks, but still how would I lazyload background images?
Lazysizes has a bgset plugin for that: https://github.com/aFarkas/lazysizes/tree/gh-pages/plugins/bgset
This is all good and fine, but I wonder how many sites have the resources and time to create image sets for all of their content images?
Methinks quite a percentage can only afford to go with one image format, at one resolution.
Yeah, probably true. But hey, now we can’t blame CSS for not having the option to do it.
Well, if you use some cdn to host your media files, they may have a conversion on fly. For eg: `https://uploadcare.com/`. You only need to upload the largest image and you can request any size/quality on fly.
I personally prefer not using background at all anymore due to accessibility reasons andas u still can’t put any SEO related Metadata on a background image. For what most people use background images for you can nowadays easily use object-fit + object-position.
The only real usecase these days are repeating backgrounds imo.
But still good to know that there is finally a solution for the performance issue that came with it for a long time.
According to Can I Use, the type() function is supported by all the major browsers /except/ Safari (desktop & iOS). See https://caniuse.com/css-image-set
If I use the type() function, will a non-supporting browser (i.e., Safari) still honor the rest of the rule? Or do I need to write separate rules for Safari, e.g.,
.div1 {
background-image: image-set(
"kitten.avif" type("image/avif") 1x,
"kitten.jpg" type("image/jpeg") 1x,
"kitten2x.avif" type("image/avif") 2x,
"kitten2x.jpg" type("image/jpeg") 2x,
"kitten.jpg" 1x, // for Safari
"kitten2x.jpg" 2x // for Safari
);
}
I have tried this solution but it does not work. No matter what the jpg file loads.
background-image: url("../../assets/images/banner2.jpg");
background-image: linear-gradient(to top, rgba(black, 0.6), rgba(black, 0.6)),
-webkit-image-set(url("../../assets/images/banner2.webp") 1x);
background-image: linear-gradient(to top, rgba(black, 0.6), rgba(black, 0.6)),
image-set(url("../../assets/images/banner2.webp") 1x);
This is my code. I even tried adding type().
Thanks Ollie
It works perfectly
How can we use media queries like this inline? the 1x and 2x don’t do this.
The only way I can work out is to create a class and then add them within dedicated media queries – but I can’t find a way to do this inline.
I am on a new macbook with chrome Version 102.0.5005.61, which is supposed to be fine. However I get ‘invalid property value’ and it uses the fallback option. Even if I paste the example here with the absolute image urls. Any ideas why this could be?
I noticed in the mdn docs it sometimes uses
background-image: image-set(
url(“large-balloons.avif”) type(“image/avif”),
and sometimes
background-image: image-set(
“large-balloons.avif” type(“image/avif”),
Tried both, no joy. Any ideas?
Hi Ben. The article is a bit misleading in that Ollie says that you can’t use
type()
with Chrome which is correct, but then follows it up with an example that usestype()
which won’t work.image-set
in Chrome (at the time of writing this)must be prefixed,
must wrap the url in the url() function
must have a dppx value e.g. 1x, 2x.
and must not have a type()
Any other combination has failed in my testing.
This means that, currently, image-set() is only useful for setting the pixel density of the image to be used, except on Firefox.
So to re-write his example, it would need to be more like this:
Realistically, for a purely static site, I reckon WEBP is the only worthwhile format we can currently use with Chrome and image-set(). The reason is that Edge supports -webkit-image-set (being a Chromium-based browser) but doesn’t support AVIF yet. Since the syntax would be correct but the file type is unsupported, Edge would just ignore the background image and not fail over to the JPG on line 6. (I haven’t tested it directly but that’s generally what happens where the syntax is right but there’s something wrong with the file itself e.g. corruption or a 404, but I’m happy to be corrected.)
Additionally, without the type() function to tell the browser what other mime types are available, there isn’t any point in providing any other options. The good news is that WEBP is very well supported now but if the browser doesn’t, then it likely won’t support image-set either, so it will fail over to the JPG in that case.
Having said all that, if you can use a
<picture>
element as Ollie suggests or an image CDN, you can side-step all of this.HTH