HTML is only part of the picture when it comes to displaying images on a webpage. The CSS styling for the website also affects the situation there.

  • It can affect whether images in the HTML are displayed or not
  • and also bring in new ones as backgrounds, borders…

To keep things feeling fast for users, we need to take care not to load extra unnecessary bytes here too.

After dealing with the responsive images in the HTML, let’s see what we can do to mitigate the impact of the CSS when displaying images.

display:none is not download:none

In CSS, the display: none declaration removes elements it affects from the document. Visually, it’s like they had never been there. Unfortunately, they were indeed there in the first place, at the time the browser was picking the stylesheets, scripts and images to download.

So despite being completely invisible to users, images hidden with display:none still trigger a network request in a background. It just wastes bandwidth for something the user will never see, delaying requests for resources that are actually needed. This might sound insignificant from the comfort of a good DSL connection.  But it’s pretty common to hide images to save space on narrower viewports, like a mobile phone, that might access the website on a smaller bandwidth connection.

To prevent this extra download, we can use the srcset and sizes attribute. We can’t just leave a gap in the list of sizes for when we don’t need an image – browsers would just pick the last size in the list as a default. But we won’t get a download from the browser if we:

  • sneak a dataURL for a 1×1 GIF the browser won’t have to download into the srcset
  • add a media query in the sizes list to use it when we don’t want the image downloaded;
<img src="//via.placeholder.com/300x300"
     height="300"
     width="300"
     srcset="//via.placeholder.com/800x800 800w,
             //via.placeholder.com/400x400 400w,
             data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 399w"
     sizes="(min-width: 400px) 100vw, 1px" />

Notice that while the sizes requires a 1px image, the srcsetannounces the empty image at a higher size: 1px below the width of the first “real” image. Browsers account for the screen DPI when picking which source to load. So for sizesrequiring a 1px image, browsers would actually be looking for 2, 3 or 4px (and more in the future) wide images sources. They’d jump to the next URL on the list and trigger an unnecessary download again.

This means the technique will only work down to a certain width. But if we’re talking avoiding downloads of images a few hundreds of pixel wide, there’s a good time ahead before we see screens with two or three digits DPI.

This shaves some unnecessary bytes for the images that get hidden by CSS. Now let’s have a look at the images CSS brings in.

The images brought in by CSS

With media queries, CSS is well equipped to pick different images (background, border, cursor…) for different viewport configurations.

Contrary to the images brought in by HTML, though, browsers don’t have any magic to pick higher resolution images on their own. We need to write extra media queries to get high-res images to screens with higher DPI.

.target {
  background-image: url('funny-cat.400x300.gif');
}
 
@media
  (-webkit-min-device-pixel-ratio: 2),
  (min-resolution: 192dpi) {

  .target {
    background-image: url('funny-cat.800x600.gif');
  }
}
 
/* And additional sizes for each DPI you decide to support */

Multiply this with a few different width of images to adapt to the viewport sizes and things quickly become really verbose. It’s a good candidate for a mixin/function/template (depending on where/how you generate the CSS) to keep things readable.

If you build your CSS through PostCSS as we do on some of our projects, you can use the postcss-image-set plugin. It lets you use the new image-set syntax brought by CSS Images Level 4 right now and keep things tidy:

/* So much nicer to read */
.target {
  background-image: image-set(url('funny-cat.400x300.gif') 1x,
                              url('funny-cat.800x600.gif') 2x);
}

The syntax only takes care of picking images according to the screen DPI, not the viewport width, but it’s already a vast improvement in readability.

In terms of downloads, modern browsers do a good job of:

  • downloading only the images actually in use because their selector matches elements from the page,
  • accounting for the cascade overriding some declarations and downloading only images from declarations that are actually styling the elements.

This was not always the case with mobile browsers, which used to struggle with that and download more images than expected when media queries were overriding. It looks like things have been sorted since Tim Kadlec’s experiment (though we could only test with a limited list of devices).

This sums up the two main impact CSS has regarding images. Hopefully, these techniques will help you keep control of the download size of the images affected by CSS and keep the experience fast for your users.