Easily Implement 2x Retina Background Images Using SCSS

Here’s the problem. You have a background image somewhere on your website that looks great on your trusty, old-fashioned desktop PC monitor, but appears blurry or pixelated on your Retina display iPad (or your iPhone, or your Samsung Galaxy S6, or any other high-density display— including 4K desktop monitors with a scaled resolution). The pixel density (often measured in PPI, or pixels per inch) on those devices is far higher than it is on your traditional, modest-resolution screen; in order for your background image to look clear and crisp on such high-PPI displays, you’re going to need to supply them with a higher-resolution image.

The solution, of course, isn’t to just use an image that’s twice the size than it needs to be for regular, 1x density displays. You only want to make people download a giant image when it’s really necessary, you jerk! But you also don’t want those with the latest and greatest technology in their hands to have a poor user experience, fully of awful-looking images. What ever will you do?

CSS Media Queries Make It Possible

Aha! With CSS media queries, you can simply supply a 2x version of your background image for the folks that need it, leaving the rest to see the normal, 1x image. All it takes is… well, more than a line or two of CSS.

First, you’d need the style directives for your regular, 1x background image (and yes, it’s best to list them out separately, rather than on one one line using the background directive, since we’re going to need to specify the background-image and background-size directives later):

div.background { 
  background-image: url(./images/yourimage.jpg);
  background-position: center center;
  background-repeat: no-repeat;
}

But then, you’d need to include a media query which will allow you to supply some additional directives only to those whose device meets a minimum pixel ratio, effectively supplying a double-resolution image and scaling it down to the same width and height of the original, 1x image:

@media all and (-webkit-min-device-pixel-ratio : 1.5),
 all and (-o-min-device-pixel-ratio: 3/2),
 all and (min--moz-device-pixel-ratio: 1.5),
 all and (min-device-pixel-ratio: 1.5) {
 
  div.background {
    background-image: url(./images/yourimage@2x.jpg);
    background-size: 200px 200px;
  }
}

This is great, don’t get me wrong. But writing all this CSS can also be a little tedious— especially when you need to do it for a dozen or so background images and you’re operating under a tight deadline.

Sass / SCSS Makes It Easy

If you aren’t already using Sass, you really should be. Seriously. It’s incredibly easy to learn, especially if you’re already used to CSS and decide to use the SCSS syntax (which I prefer, although there is also a more HAML-like, indented Sass syntax available as well). I’ve mentioned SCSS before in my introductory post, and I’ll probably be writing much more about it in the future. For now, however, let’s just say that it was made for moments like this.

First of all, make sure that your 2x resolution images are being kept in the same directory as your 1x images, and that they’re named appropriately using the @2x naming convention suggested by Apple. This means that if your original image file is at /images/test.png, your double-resolution image file should be at /images/test@2x.png.

One of the many benefits of Sass is that it gives you the ability to use mixins, which can save you a lot of time and keep your code looking much cleaner. This mixin certainly does both of those things. All you need to do is copy and paste the following bit somewhere in your SCSS (preferably a _mixins.scss file, or at least somewhere separate from where you’re actually writing your style directives, in order to keep things neat and tidy):

/****************************
 HIGH PPI DISPLAY BACKGROUNDS
*****************************/

@mixin background-2x($path, $ext: "png", $w: auto, $h: auto, $pos: left top, $repeat: no-repeat) {

  $at1x_path: "#{$path}.#{$ext}";
  $at2x_path: "#{$path}@2x.#{$ext}";
 
  background-image: url("#{$at1x_path}");
  background-size: $w $h;
  background-position: $pos;
  background-repeat: $repeat;
 
  @media all and (-webkit-min-device-pixel-ratio : 1.5),
  all and (-o-min-device-pixel-ratio: 3/2),
  all and (min--moz-device-pixel-ratio: 1.5),
  all and (min-device-pixel-ratio: 1.5) {
    background-image: url("#{$at2x_path}"); 
  }
}

You only need to copy this mixin over once per site, as when it comes time to actually implement your background images, you will now just need to include one line in your SCSS style directives for each image, like so:

div.background {
  @include background-2x( 'path/to/image', 'jpg', 100px, 100px, center center, repeat-x );
}

To break things down, you’re specifying six arguments here:

  • Path to your image: This is without the file extension and/or the ‘@2x’ flag. In other words, if your original image is at ./assets/images/background.jpg and your 2x image is where it should be at ./assets/images/background@2x.jpg, the path you’d use here would just be ./assets/images/background.
  • Extension: This is the file extension of both your 1x and 2x files, without the “dot” (e.g. just “png” or “jpg”). Defaults to png when not specified.
  • Width: This is the width of your image at 1x. It will be used to ensure that your 2x image scales down to the right size. Defaults to auto.
  • Height: This is the height of your image at 1x. It will be used to ensure that your 2x image maintains the correct aspect ratio and isn’t “scrunched” or “stretched.” Defaults to auto.
  • Background Position: This is just like what you’d enter for the normal CSS background-position property (e.g. “left top” or “center 20px”). Defaults to left top.
  • Background Repeat: This is just like what you’d enter for the normal CSS background-repeat property (e.g. “repeat-x” or “no-repeat”). Defaults to no-repeat.

This bit will handle both your 1x and 2x background images, effectively compiling into the CSS shown in the example above, but with a lot less effort for each individual image. Now, instead of having to remember how to do cross-browser retina images or write/copy the same CSS over and over, you have a simple, one-line solution in SCSS.

Of course, it’s easy to go overboard here. Don’t load your mobile users up with a bunch of ultra-high resolution background images that will swallow up what’s left of their cellular data plan. Used with wisdom and restraint, this snippet can make your life easier. Used carelessly— well, I feel sorry for your visitors. 🙂