Writing on the principles & practice of building digital products

A stab at a retina sprite workflow

Working with CSS sprites in two resolutions can quickly become unwieldy. We've set up a workflow to make things as easy as possible for ourselves.

I'll assume you're on board with the idea of using CSS sprites and that they just make sense. So let's kick things off by making a sprite file. I’m using Illustrator in this example but the same ideas apply whatever you’re using, so go nuts.

You'll want to begin by making a canvas for all your sprites to sit on. Choose a sensible size which will allow you to add a few sprites as the project grows (you can always resize it later anyway). I’ve gone for 500px by 200px here.

Sprite file in illustrator

It's worth saying that you should add new sprites to the spritesheet from the top left out, leaving existing sprites where they are. This way the position of original sprites will always be the same in your CSS.

Once you’ve added your sprites to your spritesheet, the next step is to save the file out as a transparent PNG in two sizes:

  1. A standard spritesheet, named sprites@1x.png
  2. A retina spritesheet, exactly twice the size of the standard spritesheet, named sprites@2x.png.

Our sprites are now ready to use. Lovely. Time to write some code. Let's create a Sass placeholder rule called %sprite, with the basic CSS rules we want all sprites to have:

%sprite {
  display: inline-block;
  // We're referencing the retina sprite here (sprite@2x.png)
  background-image: url('/path/to/sprites@2x.png'); 
  // ...but using the dimensions from 
  // the standard sprite (sprite@1x.png)
  background-size: 500px 200px; 
  background-repeat: no-repeat;
}

The important thing to note here is that we're effectively scaling the retina sprite to half it's natural size. This means we can use it on standard screens with no problem but still have a sharp image on retina screens. At this point you could also add a media query to only use the retina sprite on retina screens, and serve the standard sprite as default. This will save bandwidth when the retina sprite isn’t necessary.

%sprite {
  display: inline-block;
  background-image: url('/path/to/sprites@1x.png'); 
  background-size: 500px 200px; 
  background-repeat: no-repeat;

  @media (-webkit-min-device-pixel-ratio: 2), 
    (min-resolution: 192dpi) { 
    // only show retina sprite where needed
    background-image: url('/path/to/sprites@2x.png'); 
  }
}

Next, point yourself over to Sprite Cow and upload the sprites@1x.png (standard) spritesheet. This web-based sprite tool lets you 'lasso' your sprites and get pixel values for their position. As an alternative to the nudge-css-values-in-dev-tools approach, you can't beat it.

Spritecow in action

This is where the magic of having a 1x and 2x spritesheet comes into play. We now know the position of the triangle sprite on the 1x spritesheet, but because the retina spritesheet it exactly double the size and is being scaled down, the same measurements apply.

So, we can now take the measurements Sprite Cow gave us and create a new .sprite--triangle class:

// Base sprite styling
%sprite {
  display: inline-block;
  background-image: url('/path/to/sprites@1x.png'); 
  background-size: 500px 200px; 
  background-repeat: no-repeat;

  @media (-webkit-min-device-pixel-ratio: 2), 
    (min-resolution: 192dpi) { 
    background-image: url('/path/to/sprites@2x.png'); 
  }
}

// Triangle sprite
.sprite--triangle {
  // Extend the base sprite style above
  @extend %sprite; 
  // Apply custom position specific to the triangle
  background-position: -120px -9px;
  width: 43px;
  height: 43px;
}

You can now let the .sprite--triangle class run riot over your HTML, safe in the knowledge it will be serving standard sprites where you need it to, and retina where appropriate. The process for adding new sprites from here is simple:

  1. Add the new sprite to your source file and save out at 1x and 2x size
  2. Upload to Sprite Cow and get the sprite dimensions/positions
  3. Create a class for the sprite in CSS in the same format as .sprite--triangle

Another plus is that if you need to resize the spritesheet, either to add more sprites or to remove blank space, you only need change the background-size attribute on the %sprite placeholder class.

And there you have it. It's not glamorous, but having a workflow in place for dealing with retina sprites has certainly made working with various resolutions less painful.