Avoid loading unnecessary CSS background images

While working on a responsive website the question came up whether CSS background images are being loaded by the browser if the element they are applied to is set to display: none;

My initial reaction was that I thought they will be loaded even though they aren’t rendered by the browser. After creating a test case on CodePen (see the code below), I was proven right – sadly. All CSS background images will be loaded by the browser no matter if they are rendered on the page or not.

BUT this isn’t true for Firefox, which is much smarter and always only loads the images that are actually rendered.

<div class="box box--s"></div>

<div class="box box--m"></div>

<div class="box box--l"></div>
.box {
  margin: 0 auto 10px;
  width: 100px;
  height: 100px;
}

.box--s {
  background: url(s.gif);
  
  @media (min-width: 499px) {
    display: none;
  }
}

.box--m {
  display: none;
  background: url(m.gif);
  
  @media (min-width: 500px) and (max-width: 799px) {
    display: block;
  }
}

.box--l {
  display: none;
  background: url(l.gif);

  @media (min-width: 800px) {
    display: block;
  }
}

Test it yourself with this CodePen.
Let’s have a look at what we can do to avoid loading unnecessary CSS background-images.

Instead of creating three different elements and using display: none; to hide them you can just change the background image according to media queries. This will only load the image that is actually displayed.

<div class="box box--s-m-l"></div>
.box {
  margin: 0 auto 10px;
  width: 100px;
  height: 100px;
}

.box--s-m-l {
  margin-bottom: 40px;
  background: url(s.gif);
  
  @media (min-width: 500px) {
    background: url(m.gif);
  }
  
  @media (min-width: 800px) {
    background: url(l.gif);
  }
}

CodePen Testcase.

In this special case this will work, but let’s say you have three boxes with different content and different background images and want to show either one according to viewport size, just using display: none; won’t do the trick. That’s sad, but it’s the way it is, at least for now.

What you can do is set all boxes to display: none; and only when the media query applies show the box and set the background image. This way you only load the image you are actually showing and you can have different boxes.

<div class="box box--s"></div>

<div class="box box--m"></div>

<div class="box box--l"></div>
.box {
  display: none;
  margin: 0 auto 10px;
  width: 100px;
  height: 100px;
}

.box--s {
  @media (max-width: 499px) {
    display: block;
    background: url(s.gif);
  }
}

.box--m {  
  @media (min-width: 500px) and (max-width: 799px) {
    display: block;
    background: url(m.gif);
  }
}

.box--l {
  @media (min-width: 800px) {
    display: block;
    background: url(l.gif);
  }
}

Try it with this CodePen.