Skip to main content

Support Retina images on your site using SASS/Compass

Retina HDPI images using SASS Compass
Too Long?
See the abridged version

First of all, for clarity, I should say that I have lured you here with the word Retina but from now on I'm gonna be using the term HDPI as "Retina display" is actually a brand name used by Apple.

Why support HDPI?

If you a going to the trouble of building responsive website supporting all sorts of devices, you might be missing a trick if you aren't also supporting HDPI screens.

These devices will make the HDPI elements on your website look super crisp and lovely but they will also make unsupported elements stick out like a sore thumb.

OK, what are my options?

Well, CSS3 properties like box-shadow and linear-gradient are going to be HDPI right out of the box. Fonts are also automatically HDPI so using icon fonts is another great option, I definitely recommend checking out the IcoMoon website.

In terms of images though, there are a lot of different methods floating around out there.

I'm sure that everyone knows about using max-width to resize images by now - this method assumes that you are using an image that is actually twice the size (more or less) you want it to appear and then shrinking it. This has it's shortcomings though, for one you are constantly loading images twice the required size even on non retina devices. You are also manipulating an actual <img> element which can sometimes get you into a bit of trouble.

The preferred method is to use media queries and CSS background images with the background-size property. This is a much more flexible approach, giving you more control over the image with CSS and allowing you to only load the larger images on devices that support HDPI using media queries. Doing this all manually can be a fair bit of work but thankfully SASS and Compass have our back.

SASS and Compass to the rescue!

Well, almost... I should preface this by saying that HDPI image support isn't actually a built in feature of Compass just yet and there still isn't a unanimously agreed way of doing it but people seem to be using more-or-less the same general approach.

For a lot of people this means making their own custom mixins or copying someone else's from online, and there are several out there, all with their own variations of doing the same thing. I made a point of trying out as many of these that I could before and I eventually came across the Compass HDPI extension by pierreburel on GitHub.

This extension seems to combine the best elements of all of the other approaches and it's also the most true to core compass functionality.

Compass HDPI is an great way to add support for HDPI background images and sprites.

Installing Compass HDPI

With Bundler

Compass HDPI is a Compass extension ruby gem that can be installed with the rest of your gems by simply adding it to your gemfile and using bundler:

gem "compass-hdpi", :git => "git://github.com/pierreburel/compass-hdpi.git"

And run:

bundle install

Then, you need to add the following line in your config.rb file:

require "compass-hdpi"

Manually

If you have any trouble installing the gem this way you could always just download a copy of stylesheets/_compass-hdpi.scss into your sass directory instead.

Compass HDPI Mixins

Apart from a few extra mixins I'm not going to cover here, Compass HDPI provides two main mixins for HDPI image support: sprite-hdpi and background-image-hdpi.

sprite-hdpi()

Arguments

  • $map: normal sprite-map
  • $map-hdpi: HDPI sprite-map
  • $sprite: sprite name
  • $dimensions: set element dimensions based on sprite size (boolean, default to false)
  • $offset-x: set the offset for the x axis
  • $offset-y: set the offset for the y axis
  • $set-background: set element default background-image and background-repeat (boolean, default to false)

The sprite-hdpi mixin is designed to be as much like the standard compass spriting as possible. If you don't know about the standard Compass spriting procedure I would consider this recommend reading - it does help understand the way this mixin works but its not essential. The important thing to know is that Compass actually generates sprites for you. Amazing.

So you wanna use this mixin when:

  • The element you are working with is the same size as the background image you are applying to it. 
  • You have several smaller images you want to use - for example share icons.
  • The images will be commonly used.

Sprites are great because the browser only has to make one HTTP request to load the image - making good use of sprites is gonna speed up your page load times for sure.

Example usage

First of all you need to create two new folders inside your images folder - one for your standard size images and one for your HDPI (double size) images. In this example let's call them icons and icons-x2.

So for each image you put in the icons folder you should put one twice the size with the same filename in the icons-x2 folder, example: icons/facebook.png and icons-x2/facebook.png. These images will need to be .png files - this is a standard for sprites set up by Compass.

Now in your SASS file you will need:

@import "compass-hdpi"; // OR IMPORT THE _compass-hdpi.scss file if you chose manual install.

$icons: sprite-map("icons/*.png");
$icons-hdpi: sprite-map("icons-x2/*.png");

.icon-facebook {
  @include sprite-hdpi($icons, $icons-hdpi, facebook, true, 0, 0, true);
}
.icon-twitter {
  @include sprite-hdpi($icons, $icons-hdpi, twitter, true, 0, 0, true);
}

Note that the $icons and $icons-hdpi variables only need to be set once, this is what makes Compass actually generate the sprite maps. 

Personally, the way I use this mixin, I think the $dimensions and $set-background arguments should default to true (especially the dimensions so you don't see the other images in the sprite map) but unfortunately they don't so if you want these styles you will need to set them.

If you are only going to be using one set of sprite maps you could always make a shortcut mixin to set all of the arguments for you like so:

@mixin quick-sprite-hdpi($sprite) {
  @include sprite-hdpi($icons, $icons-hdpi, $sprite, true, 0, 0, true);
}

This shortcut makes calling the sprite-hdpi mixin less of a handful and makes the above code a bit simpler:

@import "compass-hdpi"; // OR IMPORT THE _compass-hdpi.scss file if you chose manual install.

$icons: sprite-map("icons/*.png");
$icons-hdpi: sprite-map("icons-x2/*.png");

.icon-facebook {
  @include quick-sprite-hdpi(facebook);
}
.icon-twitter {
  @include quick-sprite-hdpi(twitter);
}

Handy tip: If you are going to be hiding the text inside your elements, make use of the handy hide-text mixin.

background-image-hdpi()

Arguments

  • $image: normal image path
  • $image-hdpi: HDPI image path
  • $dimensions: set element dimensions based on image size (boolean, default to false)

The background-image-hdpi mixin is for any other HDPI background image you want to set but you don't want to be part of a sprite. This time you can put the images wherever you like inside your images directory.

Example usage

This example assumes your regular size image is called logo.png and your HDPI image is called logo-x2.png and they are both in the logos subdirectory of your images folder, you could call them whatever you want, though I would recommend following some kind of naming convention such as -x2.

@import "compass-hdpi"; // OR IMPORT THE _compass-hdpi.scss file if you chose manual install.

.logo {
  @include background-image-hdpi("logos/logo.png", "logos/logo-x2.png");
}

This mixin is a lot simpler, with fewer arguments. Again, you could make a shortcut mixin for this assuming your HDPI image follows your naming convention but it doesn't seem as necessary here.

Conclusion

While it's still not perfect (for me, making a shortcut mixin for sprite-hdpi is essential) I think Compass HDPI is your best bet for HDPI images. 

I do think it would be a step in the right direction for Compass HDPI to be published on rubygems.org, making it more straightforward to install. The easier it is to install the more people will use it and the more people that use it, the better it would become.

That's it really, this is quite a lot of information to squeeze into a readable blog post so let me know if you have any questions!