Cross-browser Sass & Compass media query mixins for HiDPI images

Cross-browser Sass & Compass media query mixins for HiDPI images

With the recent surge in high-density displays, such as Apple’s Retina, a lot has been written about media-queries to target these devices. High DPI images are definitely here to stay. I have written a two small media query mixins to help inserting HiDPI graphics.

There are two ways to query the density min-device-pixel-ratio and min-resolution, both with only partial (and buggy) support across devices. To make matters worse, some browsers require vendor-extensions.

This adds quite a bit of bloat to our style-sheets. Something like this, from 37signals, is common:

.logo {
  background: url("logo.png") no-repeat;
}
@media 
  (min--moz-device-pixel-ratio: 1.25), 
  (-o-min-device-pixel-ratio: 5/4), 
  (-webkit-min-device-pixel-ratio: 1.25), 
  (min-device-pixel-ratio: 1.25), 
  (min-resolution: 1.25dppx) {
  .logo {
    background-image: url("logo@2x.png");
    background-size: 100px 50px;
  }
}

Note: there’s nothing wrong with this, but it could be shorter.

Slimmed down media query

When using dpi instead of dppx, min-resolution is supported by Mozilla on Android and by Internet Explorer on Windows Phone 8. And while on a first glance min-device-pixel-ratio looks like a standard, it’s not! At this time no browser uses it, and it’s not in the spec. We could leave it in on the off-chance the W3C adds it, but my money is on min-resolution. Let’s chuck it out:

.logo {
  background: url("logo.png") no-repeat;
}
@media 
only screen and (-webkit-min-device-pixel-ratio: 1.25), 
only screen and (-o-min-device-pixel-ratio: 5/4), 
only screen and (min-resolution: 120dpi) { 
  .logo {
    background-image: url("logo@2x.png");
    background-size: 100px 50px;
  }
}

What about print?

Printers typically use a higher resolution as well. So if we go through all the trouble of creating our high-density images, we might as well use them for printing. We add print and remove only screen:

.logo {
  background: url("logo.png") no-repeat;
}
@media print,
  (-webkit-min-device-pixel-ratio: 1.25),
  (-o-min-device-pixel-ratio: 5/4),
  (min-resolution: 120dpi) { 
  .logo {
    background-image: url("logo@2x.png");
    background-size: 100px 50px;
  }
}

Nicer, right? And coincidentally also very similar to to the high-density media query in the WordPress core.

Sass media query mixin

We could throw the media query in the bottom of our stylesheet and overwrite all our images there in one place. But, at least while developing, I prefer to have the media queries together with the default background image. More bloat, but easier maintenance.

For that I use the following Sass mixin:

@mixin hr-image($image, $width, $height) {
  @media print,
         (-webkit-min-device-pixel-ratio: 1.25),
         (-o-min-device-pixel-ratio: 5/4),
         (min-resolution: 120dpi) { 
    background-image: url($image);
    background-size: $width $height;
  }
}

Which can be called like this:

.logo {
  background: url("logo.png") no-repeat;
  @include hr-image("logo@2x.png", 100px, 50px);
}

View on Github Gist

Compass media query mixin

Compass can calculate the image size for us, so we can make it even easier to use:

@mixin hr-image($lr-image, $hr-image) {
  background-image: url($lr-image);
  @media print,
         (-webkit-min-device-pixel-ratio: 1.25),
         (-o-min-device-pixel-ratio: 5/4),
         (min-resolution: 120dpi) { 
    background-image: url($hr-image);
    background-size: image-width($lr-image) image-height($lr-image);
  }
}

Which can be called in just one line, like this:

.logo {
  @include hr-image("logo.png", "logo@2x.png");
}

View on Github Gist

Related articles, research & inspiration: