A couple of weeks ago Chris wrote about Designing for Retina, which covered Mac App design. The same principles apply to designing images for the web. So if you haven’t yet, go and read Chris’ post as it’ll give you a good idea of what you need to consider when designing retina images. In this post we’ll look at how you can implement retina images on your website.
Principles
When you're writing CSS, you need to be careful to only download retina graphics when they're really needed. This is done by detecting what’s known as the “pixel ratio”. Very simply, this is the number of physical pixels vertically and horizontally that render each virtual pixel (more commonly known as “points”). Non-retina devices (such as the iPhone 3GS or current MacBook Air) will have a pixel ratio of 1, whilst newer retina devices (iPhone 4 onwards) have a pixel ratio of 2. If you’re wanting to learn more about pixel ratios, I suggest watching Session 602 - Delivering Web Content on High Resolution Displays on Apple’s Developer site.
There are currently three ways to implement retina images; via the CSS background-image property, with Javascript or using the new Image Set CSS function. We’ll cover all three below.
Oh, just a quick note: I append the Apple standard “@2x” to the retina image name. So “cats.png” becomes “cats@2x.png” on a retina device.
CSS Images
This option works for any HTML element you want to set a CSS background image on.
To do this, set your element styles as you would normally including the background-image property. Then add a media query that checks the min-device-pixel-ratio is 2 and set the background-image to use the retina image.
It’s important to remember that you must include the background-size property in the base styles for the element, as the browser needs to know the original size so it can scale the retina image appropriately.
Note: I’ve removed browser specific prefixes on all code examples for simplicity.
/* Standard styles for your element
Be sure to include the background-size property
*/
.cats {
…
background-size: 400px 200px;
background-image: url(cats.png);
}
@media screen and (min-device-pixel-ratio: 2) {
/* Set the retina background */
.cats {
background-image: url(cats@2x.png);
}
}
This is nice and easy and it’s great for performance as the browser only downloads the image needed for the device, rather than both. The browser will also automatically update the images when dragging the browser window between a non-retina and retina display (say a Retina MBP and the current Cinema Displays). The downside is that it’s not great for accessibility to serve all your images (in a sprite or individually) this way, but you need to make that decision based on your project.
Inline Images with Javascript
Inline images are tricker to deal with. Currently there’s no way to tell the browser which image you want it to display for the device. What you could do is check the pixel ratio on page load and create all the inline images you need based on that, then insert them in to the DOM. I don’t like this solution as I feel it’s a bad user experience: the images flash in to view after the rest of the content had loaded so I’m not going to cover this technique here.
Instead, what I did on the Analog page was add a matchMedia listener to the window and update all the images on the page as if it was a retina device.
Note: In this example I loop through all <img> elements on the page, but you'll probably want to selectively replace images which you could do via a class or data- attribute selector.
function retinaise() {
// Check if it's a retina device or not
var retina = (window.devicePixelRatio > 1) ? true : false;
// Loop through all the images you want to update
$("img").each(function(i,image) {
var source = image.getAttribute('src');
// Append "@2x" to the image src if it's a retina device
// else remove the appended "@2x" if it's a non-retina device
if (retina == true) {
source = source.replace(/\.\w+$/, function(match) { return "@2x" + match; });
} else {
source = source.replace(/(@2x)/, '');
}
// Set the image src
image.setAttribute('src', source);
});
}
// Add a listener for the device pixel ratio
window.matchMedia("(device-pixel-ratio: 1)").addListener(retinaise);
This is great for accessibility; all your images are image elements on the page. It’s also great as the images will update in the same way as the CSS background images technique when dragging the browser window between displays. This technique isn’t so hot when it comes to performance as you’ll be loading both the standard and retina versions of every image. Again, this is something you need to decide on based on your project spec and target audience.
Something you should consider using is retina.js, it’s an open source project that “makes it easy to serve high-resolution images to devices with retina displays”.
CSS Imageset
Finally there is a new CSS function, created by Apple, called Image Set.
Image Set allows you to specify a set of images for the CSS element in question, and tell the browser the pixel ratio you want to target, like this:
.cats {
background-image: -webkit-image-set(
url(cats.png) 1x,
url(cats@2x.png) 2x
);
background-size: 400px 200px;
}
This is lovely as you can list multiple images in one place and the browser applies the most relevant image for the device. What isn’t so lovely is that it’s currently only available in Safari 6, Safari on iOS 6 & Chrome 21. It’s also not great for accessibility as mentioned in the CSS images section above.
So that wraps up what I believe are the most relevant techniques for retinaising your images on the web. You’ll need to choose between them depending on the needs of your project.
If you found this post helpful, think I’ve missed something or have other ideas on implementing retina images, please let me know on Twitter or leave a comment below.