Skip to content

Compass sprites and retina displays #sass

I’ve been doing a lot more work lately on the mobile web, that is, specifically targeting mobile devices.

As if I needed more reasons to love Compass, it also makes it easy to optimize your sprites for retina and non-retina displays. Adam Brodzinski has open sourced a @mixin that will rock your world.

Back up — remind me what the deal with retina displays is?

Oh, don’t mind if I do! Retina displays show four times the amount of pixels than a standard display. Think of it as a box to help visualize it in your head:

Standard display 1×1:
X

Retina — 2×2:
XX
XX

So when you show a regular image on a retina display, it can sometimes look very pixelated, because instead of showing 4 pixels in that space (4 X’s), you’re stretching the one pixel (our single X). That can look pretty wonky.

The way to make it NOT look wonky is to simply double the size of your images, and scale the background-size property accordingly. But why do that on non-retina displays? They shouldn’t be burdened with oversized images when they don’t need them.

Now we’re ready for the mixin (I’m showing a simplified version here, the GitHub repo has more options).

@mixin retina-optimized-sprite($name, $sprites, $sprites2x) {
  background-image: sprite-url($sprites);
  background-position: sprite-position($sprites, $name);
  background-repeat: no-repeat;
  @media (-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-device-pixel-ratio: 1.5) {
    & {
      $pos: sprite-position($sprites2x, $name);
      background-image: sprite-url($sprites2x);
      background-position: nth($pos, 1) nth($pos, 2) / 2;
      @include background-size(ceil(image-width(sprite-path($sprites2x)) / 2) auto);
    }
  }
}

This mixin takes as its arguments the name of a sprited image (example: ‘button’), the sprite-map for standard displays, and the sprite-map for retina displays, which you can assign once as $sprites and $sprites2x and leave out in the rest of your SCSS/Sass.

In practice let’s say I write

.button { @include retina-optimized-sprite(button); }

This mixin will display that element with the background image of the sprite, the appropriate background-position is assigned (and background-repeat: no-repeat so nothing silly happens).

Then there’s a media query:

@media (-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-device-pixel-ratio: 1.5)

This says “If the browser pixel ratio exceeds 1.5, do this,” that is, “Is this a retina display?” In the case of a retina display, we display the retina-sized (double) sprite, and programmatically calculate the correct position.

The only part I found tricky was the background-size calculation:

@include background-size(ceil(image-width(sprite-path($sprites2x)) / 2) auto);

This scales the background for you, but I was getting an error with the image size function. I used Compass’s interactive feature to check it out:

>> sprite-path($sprites)
"sprites/interaction-xxxxxxxxx.png"
>> image-width(sprite-path($sprites))
Errno::ENOENT on line ["28"] of /Users/paperlesspost/.rvm/gems/ruby-1.9.2-p318/gems/compass-0.12.2/lib/compass/sass_extensions/functions/image_size.rb: No such file or directory - /Users/paperlesspost/Sites/paperlesspost/paperless-mobile/images/sprites/interaction-sda988d5bfc.png

My problem was this sprite path didn’t actually match where my sprites were — I keep my sprites in images/generated/sprites, and it’s specified in my compass.rb configuration:

generated_images_dir = "/images/generated/"

Hmm. So here was my workaround:

$path: "generated/" + sprite-path($sprites2x);
 // give our correct base path to sprite-path
 @include background-size(ceil(image-width($path) / 2) auto);

I basically corrected for this configuration problem. I’d love to know if this is something I should change in my Compass configuration, but it works just fine now — showing retina or standard sprites exactly when it needs to.

4 Replies to “Compass sprites and retina displays #sass”

  1. Impressive! But I thought all images referenced in CSS are downloaded, regardless of what media block they’re grouped behind? Or are browsers catching up and doing conditional loading on their own more now?

    1. Not sure about how @media is impacted, but that’s a very good point! I assume not. Outside of media blocks, images referenced in CSS are displayed when rendered, which is why sprites were a big deal; no jumpiness between a button and its hover background image, because the background image was already loaded as a sprite.

  2. There’s now an app for handling sprite images and CSS for Retina Display called Spritebox – spritebox.net

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.