«
8 minute read

Retina Images That Respond

I’ve always wanted to and planned to publicly share helpful code snippets and other tips related to business and building products, but have always lacked the time. Well, no longer. I’m going to make the effort to share more. Roon makes it pretty easy ;)

First up is just a small tidbit of code I use to make all my projects support retina (high pixel density) displays. It’s nothing ground breaking and is probably already practiced by most of you reading this, but I like it because it does not rely on any Javascript framework. I always prefer to use straight up Javascript (sans-framework) when possible. Typically that means marketing and homepages. Unless I need to pull in content via AJAX or am working on an app interface , I don’t need a Javascript framework. There is always a tiny amount of Javascript (if any at all) on my marketing pages, so using straight up Javascript speeds things up.

Tips for 2013: Never animate things with Javascript, use CSS. Never show/hide/fade-in/fade-out elements in Javascript, use CSS.

Diving In

To make your pages look good on devices that have retina displays (smart phones, laptops, etc.) you need to serve up images that are twice the size of images on legacy displays (72 ppi displays). If you’re reading this on any desktop monitor that exists in 2013, you’re on a legacy display (72 pixels per inch). Retina displays are pretty new and vary in their pixel density. Bottom line is you need to serve up larger images to compensate for higher density displays.

Native browser support is on it’s way into HTML for this. For now, here is a simple way to do it with Javascript.

<img src="myimage.jpg" data-2x="myimage@2x.jpg">
<img src="photo_800.png" data-2x="photo_1600.png">

The HTML above is an example of two images on a page. I added the extra data-2x attribute to hold the location of the larger (retina) image. This should be the full URL path to the image.

if(window.devicePixelRatio >= 1.2){
    var images = document.getElementsByTagName('img');
    for(var i=0;i < images.length;i++){
        var attr = images[i].getAttribute('data-2x');
        if(attr){
            images[i].src = attr;
        }
    }
}

The Javascript above is only run when the browser is on a retina device. It then gets all the <img> elements on the page and loops through them. It checks for the images that have the data-2x attribute, and on those it replaces the image src with the retina image. Easy peasy. This means you don’t need to ever update this code when adding new images to your page. All you need to do is include the data-2x attribute on any img tags you add to the page and everything will work automatically.

The above scenario works if the only images on your page are contained in img elements. But most of the time you’ll also have elements displaying images as CSS background-image as well. Adding retina support for those is pretty easy too.

<div data-2x="background_big.jpg" style="background-image: url(background.jpg);"></div>

In the HTML above we have a <div> element that has an image displayed as a CSS background-image. It also has a data-2x attribute that holds the location to the retina image.

if(window.devicePixelRatio >= 1.2){
    var images = document.getElementsByTagName('*');
    for(var i=0;i < images.length;i++){
        var attr = images[i].getAttribute('data-2x');
        if(attr){
            images[i].style.cssText += 'background-image: url('+attr+')';
        }
    }
}

This Javascript is pretty similar to the last snippet, except that it grabs all the elements on the page and then updates the CSS background-image (while preserving any other inline styles) with the retina image. However, this approach wouldn’t work for any <img> elements on the page as it would update the background-image only and not the src, as it should with an <img> element.

Let’s look at both approaches combined into one simple snippet.

<img src="myimage.jpg" data-2x="myimage@2x.jpg">
<img src="photo_800.png" data-2x="photo_1600.png">

<div data-2x="background_big.jpg" style="background-image: url(background.jpg);"></div>

Now we have an example that resembles a real world web page. Images in both <img> elements and as CSS background-image.

if(window.devicePixelRatio >= 1.2){
    var elems = document.getElementsByTagName('*');
    for(var i=0;i < elems.length;i++){
        var attr = elems[i].getAttribute('data-2x');
        if(attr && elems[i].tagName == 'IMG'){
            elems[i].src = attr;
        } else if(attr){
            elems[i].style.cssText += 'background-image: url('+attr+')';
        }
    }
}

The Javascript here grabs all the elements on the page and then filters the results to elements with the data-2x attribute on them. It then checks to see if the element is an <img>, if so it updates the src attribute. If not it updates the CSSbackground-image.

Just for contrast, the equivalent in jQuery looks like this:

if(window.devicePixelRatio >= 1.2){
    $("[data-2x]").each(function(){
        if(this.tagName == "IMG"){
            $(this).attr("src",$(this).attr("data-2x"));
        } else {
            $(this).css({"background-image":"url("+$(this).attr("data-2x")+")"});
        }
    });
}

If the only reason you need Javascript on your web page is retina support, you don’t need jQuery.

Images That Respond

One thing you’ll want to be sure to do is set a width value for all <img> elements. When the retina image is loaded in, the <img> elements will expand to the larger size. So if an image was 400px wide before, it will now display as 800px wide. Which would most definitely mess up the layout of your page. Not only that, it completely defeats the purpose of using a retina image, since it will also appear scaled up on the retina device. So set your image widths with CSS. Alternatively, you could set the height value. But typically you don’t want to set both, here’s why.

Best practice for responsive images is to set all images to have a max-width: 100%, like this:

img {
    max-width: 100%;
}

Now when the browser window scales down or up, the image will scale as well and because browsers are awesome it will maintain it’s aspect ratio as it scales. However, if you set both a width and height value to your images, they will NOT maintain their aspect ratio. Because as the browser window scales below the image’s defined width, the max-width rule kicks in and the image will start to scale down. As it scales down the image’s width is getting smaller, but the image’s height remains the same and will NOT scale down, because you had set a specific value for it. This will make your image appear squished and it will completely lose it’s aspect ratio. So if you’re using the max-width: 100%, never not set a height. If you’re not using max-width: 100% and the image is not square, don’t set both the width and height, set one or the other. Doing this ensures you’ll preserve the image’s aspect ratio.

By setting a width value, you’re ensuring that the image will stay that size even when the retina version is loaded in it’s place. This needs to be done.

For images that appear as a CSS background-image, you’ll want to set the background-size property to “cover”. Doing this will ensure the image always fits within it’s container whether it’s a retina image or not.

[data-2x] {
    background-size: cover;
}

So there you have it. Adding retina support to your web pages is ultra easy and you should do it on every project you write code for.


See my follow up post: Responsive Retinas Strike Back

Share Comment on Twitter