The term “responsive images” has come to mean “responsive images in HTML”, in other words, the srcset
and sizes
attribute for <img>
and the <picture>
element. But how do the capabilities that these things provide map to CSS?
CSS generally wasn’t really involved in the responsive images journey of the last few years. That’s for good reason: CSS already has the tools. Responsive images was, in a sense, just catching up to what CSS could already do. Let’s take a look.
srcset
in CSS
In HTML, srcset
is like this (taken from the Picturefill site):
<img srcset="
examples/images/image-384.jpg 1x,
examples/images/image-768.jpg 2x
" alt="…">
One image for 1x displays, a larger image for 2x displays. If we wanted to do that same thing, only as a background-image
in CSS, we could do:
.img {
background-image: url(examples/images/image-384.jpg);
}
@media
(-webkit-min-device-pixel-ratio: 2),
(min-resolution: 192dpi) {
.img {
background-image: url(examples/images/image-768.jpg);
}
}
There is a difference here, though. As I understand it, the way srcset
is spec’d out is a bit like a suggestion. The attribute and value provide information about what is available and the browser decides what is best at the moment. Or at least, it could, if browsers chose to implement it that way. With a @media
query, what is declared shall be.
Resolution media queries are fairly well supported:
There is another way as well, that is actually closer to how srcset
works, and that’s using the image-set()
function in CSS:
.img {
background-image: url(examples/images/image-384.jpg);
background-image:
-webkit-image-set(
url(examples/images/image-384.jpg) 1x,
url(examples/images/image-768.jpg) 2x,
);
background-image:
image-set(
url(examples/images/image-384.jpg) 1x,
url(examples/images/image-768.jpg) 2x,
);
}
It has a little less support than resolution queries:
It’s much closer to srcset
, not only because the syntax is similar, but because it allows for the browser to have a say. According to the (still in draft) spec:
The image-set() function allows an author to ignore most of these issues, simply providing multiple resolutions of an image and letting the UA decide which is most appropriate in a given situation.
There is no perfect 1:1 replacement for srcset
in CSS, but this is pretty close.
sizes
in CSS
The sizes
attribute in HTML is very directly related to CSS. In fact, it basically says: “This is how I intend to size this image in CSS, I’m just letting you know right now because you might need this information right this second and cannot wait for CSS to download first.”
Sample:
<img
sizes="(min-width: 40em) 80vw, 100vw"
srcset=" ... "
alt="…">
Which assumes something like this in the CSS:
img {
width: 100%;
}
@media (min-width: 40em) {
/* Probably some parent element that limits the img width */
main {
width: 80%;
}
}
But sizes alone doesn’t do anything. You pair it with srcset
, which provides known widths, so the browser can make a choice. Let’s assume just a pair of images like:
<img
sizes="(min-width: 400px) 80vw, 100vw"
srcset="examples/images/small.jpg 375w,
examples/images/big.jpg 1500w"
alt="…">
The information in the markup above gives the browser what it needs to figure out the best image for it. The browser knows 1) it’s own viewport size and 2) it’s own pixel density.
Perhaps the browser viewport is 320px wide and it’s a 1x display. It now also knows it will be displaying this image at 100vw. So it has to pick between the two images provided. It does some math.
375 (size of image #1) / 320 (pixels available to show image) = 1.17
1500 (size of image #2) / 320 (pixels available to show image) = 4.69
1.17 is closer to 1 (it’s a 1x display), so the 375w image wins. It’ll try to not go under, so 1.3 would beat 0.99, as far as I understand it.
Now say it was a 2x display. That doubles the amount of pixels needed to show the images, so the math is:
375 / 640 = 0.59
1500 / 640 = 2.34
2.34 wins here, and it’ll show the 1500w image. How about a 1x display with a 1200px viewport?
375 / (80% of 1200) = 0.39
1500 / (80% of 1200) = 1.56
The 1500w image wins here.
This is kinda weird and tricky to write out in CSS. If we just think about 1x displays, we end up with logic like…
- If the viewport is less than 375px, use the 375w image.
- If the viewport is larger than 375px but less than 400px, use the 1500w image (because otherwise we’d be scaling up).
- At 400px, the image moves to 80vw wide, so it’s safe to use the 375w image for a littttttle bit ( (between 400px and 468px)
- Over 468px, use the 1500w image.
Which we could write like:
img {
background-image: url(small.jpg);
}
/* Only override this if one of the conditions for the 1500w image is met */
@media
(min-width: 375px) and (max-width: 400px),
(min-width: 468px) {
main {
background-image: url(large.jpg);
}
}
In this exact case, a 2x display, even at a really narrow width like 300px, still requires 600px make that 1.0 minimum quality, so we’d also add that to the logic:
.img {
background-image: url(small.jpg);
}
/* Only override this if one of the conditions for the 1500w image is met */
@media
(min-width: 375px) and (max-width: 400px),
(min-width: 468px),
(-webkit-min-device-pixel-ratio: 2),
(min-resolution: 192dpi) {
.img {
background-image: url(large.jpg);
}
}
The complexity of this skyrockets the more breakpoints (sizes) and the more provided images. And it’s still not a perfect match for what responsive images (in HTML) can do, since it doesn’t allow for browser discretion (e.g. the potential for a browser to consider other factors [i.e. bandwidth] to choose an image).
picture
in CSS
An example:
<picture>
<source srcset="extralarge.jpg" media="(min-width: 1000px)">
<source srcset="large.jpg" media="(min-width: 800px)">
<img srcset="medium.jpg" alt="…">
</picture>
This kind of thing is a fairly straight-forward conversion to media queries. The exact media queries are right there to copy:
.img {
background-image: url(medium.jpg);
}
@media (min-width: 800px) {
.img {
background-image: url(large.jpg);
}
}
@media (min-width: 1000px) {
.img {
background-image: url(extralarge.jpg);
}
}
No surprise, this can get more complicated, because srcset
can do it’s thing within the picture
element as well:
<picture>
<source srcset="large.jpg, extralarge.jpg 2x" media="(min-width: 800px)">
<img srcset="small.jpg, medium.jpg 2x" alt="…">
</picture>
Which translates to:
.img {
background-image: url(small.jpg);
}
@media
(-webkit-min-device-pixel-ratio: 2),
(min-resolution: 192dpi) {
.img {
background-image: url(medium.jpg);
}
}
@media
(min-width: 800px) {
.img {
background-image: url(large.jpg);
}
}
@media
(-webkit-min-device-pixel-ratio: 2) and (min-width: 800px),
(min-resolution: 192dpi) and (min-width: 800px) {
.img {
background-image: url(extralarge.jpg);
}
}
Again, this is just waiting to blow up in complexity as you add a few more images and conditions. Slightly better with image-set()
:
.img {
background-image: url(small.jpg);
background-image:
-webkit-image-set(
"small.jpg" 1x,
"medium.jpg" 2x,
);
background-image:
image-set(
"small.jpg" 1x,
"medium.jpg" 2x,
);
}
@media
(min-width: 800px) {
.img {
background-image: url(large.jpg);
background-image:
-webkit-image-set(
"large.jpg" 1x,
"extralarge.jpg" 2x,
);
background-image:
image-set(
"large.jpg" 1x,
"extralarge.jpg" 2x,
);
}
}
Could mixins help?
Probably? I’d love to see a Sass @mixin
that would take all these params, factor in the image sizes (that it figures out itself by hitting the disk), and spits out some quality responsive-images-in-CSS code. Maybe there is even a way to combine resolution queries and image-set()
syntaxes?
Are we actually doing this?
I’m curious how many people are actively handling responsive images in CSS. Perhaps even in a middle-ground kinda way of just swapping out larger images at large breakpoints? I wonder if, because picture/srcset is often automated, that actually has a higher adoption rate than responsive images in CSS does?
The srcsets were always bugging me out. Thanks, Chris!
image-set
certainly seems to be the way to go, but until browser support picks up, I have to implement it with a combination of@supports
and JavaScript detection. Worst case scenarIEo, I give the browser the mobile image and then replace it a.s.a.p. in JavaScript with a browser-resolution-dependent alternative.Thanks for the article. I find the method you mentioned at the end to be the easiest for now (swap bg images at larger breakpoints). Usually I use a mobile image and a full-size image to keep things simple.
Yeap, this is what I do. I use a simple mixing tonlosd the appropriate bg image based on the entrie on my breakpoints map. Nothing fancy but it does that job, and most importantly, I know exactly which will be loaded on a specific scenario.
Great article Chris, I didn’t know a lot of the information you covered. I’m currently just using the srcset attribute myself for my personal site (which I won’t link). I am little confused on how to use the sizes attribute… so if you could explain the syntax to me I would be really grateful. Anyway, great article.
How about using
Width: auto;
Height: auto;
Max-height: auto;
Max-width: auto;
And setting a fixed dimension to it’s parent’s based on different screen sizes.
Say
Width: 160px;
Height: 160px;
The point of this is to only load the best image for your device so you don’t have to download a very hi-resolution picture on every devices.
Thanks for the article Chris. I’m with Yaphi. I swap out background images at various break points, never really exceeding 2 or 3 images. Desktop, mobile and sometimes one extra to deal with a masthead image + cta breaking down. Keep it simple.
Hey nice post!
We’ve been using it at my company to reduce image size downloaded, especially for full screen images this can reduce the sites bandwidth from a few mb to under one mb. We mostly use html version, because all our images are uploaded runtime.
The problem I find is it’s hard to properly integrate, once using bootstrap columns, with different breakpoints.
I’d highly suggest checking out Cloudinary or ImgIX to automatically set this sort of stuff. True, you can completely set this yourself if required to, but these companies are both affordable and do some seriously amazing stuff. Btw, I am in no way affiliated nor do I get any $ for this; I’m just a user of both on various projects and its been awesome to no longer have to deal with image issues.
Regardless, thanks for the write up as usual Chris!
Great article. I’ve used srcset on a couple of websites to fix the abysmal way some browsers were handling galleries.
Worth noting:
https://github.com/square/apropos
apropos looks like a promising aproach, thanks for that!
I played around with https://github.com/paper-leaf/waldo on my recent project, admittedly this being more PHP then CSS and heavily based on WP / ACF but IMHO worth noting just the same.
For bg images, it is a lot easier to have a mixing to load tge right image at a certain breakpoint, bit what your cover on ther article in intersting for inline images. Thanks!
Good article, however, while the proposed solutions might solve some problems, background images on IMG violate html semantics and degrades accessibility.
An img is supposed to have a meaning that completes the content, while a background images are meant for decorative purpose.
In no way am I insinuating that you should move a content image to a CSS background image.