Elegantly preload background images with SASS

To display icons, I mainly use SVG’s as background images. This works fine because vector images look crisp on all screens, the SVG format is easily compressible using gzip (it’s XML after all) and using Modernizr I can provide PNGs as fallback if SVG background images are not supported. However, sometimes we need to preload an image to prevent a lag when it is used later than at page load. This post describes a way to do this elegantly using SASS.

The problem

In some cases there is the need for preloading background images. For instance, on our Hstry platform we have multiple choice quiz questions. As long as the question is not correct, the tick is blue. When the answer is correct, the tick becomes green. So in SASS the code is something like this:

.quiz-question {
  .tick-box { background-image: url('icon-tick-blue.svg'); }
  &.correct .tick-box { background-image: url('icon-tick-green.svg'); }
}

The problem is that when the page is loaded, only the icon-tick-blue.svg image is loaded because the CSS rule corresponding to the green tick is not matched. This is a browser optimisation and it makes total sense. However, what happens is that there is a lag before the green tick is shown when the user answers the question correctly because it needs to load the image from the server.

Preload images with CSS

A trick to preload images is by setting the images as content of a pseudo-element on the body element:

body:before {
  display: none;
  // both images are now loaded when the page loads
  content: url('icon-tick-blue.svg') url('icon-tick-green.svg');
}

We don’t want to add each image that we want to preload manually. Instead, every time we use a background image, we want it to be automatically added to this list. This is possible using SASS lists.

The solution in SASS

First we define a mixin background-image-svg that will:

  • set an SVG background image
  • provide a PNG fallback
  • add the SVG image to the list of preloaded images
@mixin background-image-svg($img-name, $png-directory: "png-fallback/") {
  background-image: image-url($img-name + ".svg");
  // This function will add the image to the list of images to preload.
  // The function is defined below.
  $tmp: preload-image($img-name + ".svg");
  // no-svg is defined by Modernizr
  .no-svg & { background-image: image-url($png-directory + $img-name + ".png"); }
}

We replace the code above as follows:

.quiz-question {
  .tick-box { @include background-image-svg('icon-tick-blue'); }
  &.correct .tick-box { @include background-image-svg('icon-tick-green'); }
}

Then we have to define the SASS function preload-image.

$preloaded-images: null;
@function preload-image($image-url) {
  $preloaded-images: $preloaded-images url($image-url);
  @return $preloaded-images;
}

We define a list $preloaded-images which is null initially. Every time the function is called, it appends the string url($image-url) to this list. The return call is not necessary for our purpose but is required by the SASS language.

As the SASS processor goes through the stylesheets, it builds up this $preloaded-images list every time the background-image-svg mixin is included. At the end, it will have generated this long string url(...) url(...) ... url(...). All we have to do now is output the string as the content of the pseudo-element on body!

body:before {                                                                                           
  display: none;                                                                                        
  content: $preloaded-images;                                                                           
}

And we’re done! Using SASS mixins, functions and lists, we can automatically preload an image every time it is included as a background image.

Write us your thoughts about this post. Be kind & Play nice.
  1. Elisabeth says:

    Great tutorial! But I wonder if you can provide the full sass file for this example?

    Reply
    • Yoran Brondsema says:

      Hi Elisabeth, I’m happy you liked the article! The example is part of a bigger component that we use in production, but I will try to isolate this particular example into a single file. I hope that will make it clearer. I’ll get back to you on this!

      Reply
  2. Pieter says:

    Loved the idea
    but did not work for me (old post things changed i guess ;-))

    $preloaded-images: null;
    @function preload-image($image-url) {
    $preloaded-images : $preloaded-images url($image-url) !global;
    @return $preloaded-images;
    }

    !global is needed or else $preloaded-images is scoped local to the function….

    gist
    https://gist.github.com/pieterboscapitalid/c94635d05b158b7f5e0d

    thnx!

    Reply
    • Yoran Brondsema says:

      Hi Pieter,

      Thanks for pointing that out! I haven’t checked it but I guess that later versions of SASS make variables locally scoped by default (which is definitely an improvement). Thanks!

      Reply