Responsive Retinas Strike Back

by Drew Wilson

Published

I wrote a post yesterday all about responsive retina images. Well, I can't say "all about" because I just learned that you can't post code samples on the internet without fully explaining when and how you should use the code. Whoops, my bad. I'm learning and I'll eventually get better at this code blogging stuff.

Here are a two responses to my last post from some smart developers:

Both of these guys are correct, but I'd like to talk about why and fully explain the retina image issue they're referring to.

Inline CSS

First in my previous post I had used this HTML markup as an example:

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

I was using inline CSS above purely as a clear and easy way to show that there was an image on that element already. In most cases you'd want to put the background-image rule in your external CSS file, not inline, like this:

#logo {
    background: transparent url(logo.png) no-repeat center center;
}

.icon {
    background: transparent url(sprite.png) no-repeat left top;
}

However, when you're building a website or app that pulls in content dynamically, you'd use inline CSS. Like I do on my Lumo site. Since I don't know what images will be displayed beforehand (because they're pulled dynamically from a database) on Lumo, I need to add the CSS code before the page renders. So I do that server side and put the CSS inline on the element. But if you're making a marketing or home page where you do indeed know which images are going to be used, you can put all the CSS in your external CSS file. In those cases you can use a CSS media query in your external CSS file to swap out the images with retina images like this:

@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-device-pixel-ratio: 1.5) {

    #logo {
        background-image: url(logo@2x.png);
        background-size: 110px 40px;
    }

    .icon {
        background-image: url(sprite@2x.png);
        background-size: 200px 800px;
    }

}

No need for Javascript at all here. Just CSS.

Double Whammy

By using the approach outlined in my last post, two versions of each image will download when you're on a retina device. Double whammy.

Before moving on let's be clear: As I mentioned in my last post, this issue will soon go away as HTML and browsers get native support for multiple versions of a single image. Soon none of this will be an issue. Ok, now we can move on.

In my view this isn't a huge deal. Why? Because I usually use one or two images in my interfaces. Typically there is a logo and sometimes a tiny icon sprite on the page. Most of the time I use my Pictos Server for icons. Icon fonts are great because you don't need a separate retina version, they are vector so they scale to any size automatically :) You could also use SVGs (vector) for a lot of images; especially with the flat trend going on right now.

However, there are many cases when you have a ton of images on one page. This describes pretty much every single web page on Apple's website. They use the same approach I outlined in my last post: Load in image assets and if the device is a retina, load in the retina versions and swap them out.

Depending on the number and size of images on the page, you might see low res images for a couple of seconds before the larger ones load in. But if you only have a handful of images on your site, you won't even notice the larger ones swap in because it happens so fast. And if you're smart and set your images to be cached by the browser, this extra load only happens once for the user.

Another thing to mention is that the browser will not automatically "prefetch" your retina images with this Javascript method. Why? Because the retina image URLs are put inside a data-2x attribute which means nothing to a browser. It won't assume it's an image and will do nothing with it. That's why we need a Javascript snippet to take that URL and use it as the image source.

In my experience the double whammy effect is really not that big of a deal for pages without many images. And any solution anyone comes up with is just temporary, since a native solution is already on it's way.

No-SRC Method

There is another approach I didn't mention in my last article and I call it the No-SRC method. Just like it sounds you don't add a src attribute to your <img> tags. You could also opt to not add any background-image rules up front as well. This means the browser will not download any images at all upon page load. In both cases you'd add the src and/or background-image values after the page has loaded, using Javascript. This gives you the chance to see what kind of browser is visiting your site and you can then determine if you should send low res or retina images to the browser.

The benefit here is only one version of an image is loaded instead of two for retina devices. The obvious downside is it doesn't work for browsers without Javascript (or with Javascript turned off). This includes search engines. So depending on your goals for the website you're making, this could be a great thing or a bad thing. If you want to hide images from browsers without Javascript, it's great.

Here is some example HTML for the No-SRC method:

<img data-src="logo.png" data-2x="logo@2x.png">

<div data-src="background.jpg" data-2x="background@2x.jpg"></div>

You can then use the following Javascript code, which is almost exact same snippet I mentioned in my last blog post:

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

This grabs all the elements on the page and filters them down to only elements with the attribute data-src or data-2x depending on if the browser is a retina device or not. It then adds the appropriate image version to the src attribute if the element is an <img> element, and add its to the CSS background-image rule (while preserving other inline styles) if it's not.

Also, for elements that will get an image put in them, you should add the appropriate CSS rules in your external CSS file: like background-repeat, background-color, background-size, etc. Leaving only the background-image rule to be added inline.

Here is an interesting idea for adding image support to browsers without Javascript using the No-SRC method:

Exact Images

My Lumo site is an example of the No-SRC method in action. However I use a service called ReSRC.it to handle all the image logic for me. It's great because not only does it serve retina when needed, it also serves up the images at the exact sizes needed.

Let's say you store a single image in three different size variations: 100px, 400px and 1000px. But on your website there is a spot where it needs appear in 600px container. Well you'd use the 1000px version of the image and just let the browser scale it down by giving the element a specific width or height. It works great. However, you're now downloading an image that's larger than you need. This is fine if you have a few images on a page, but if you're a site like Lumo that displays hundreds of images, that little extra KB of data adds up. The solution is to serve the image at the exact size you need. Services like ReSRC.it do just that. They dynamically resize and send the images at the exact size you need. It also is a CDN and caches any dynamically created images so future requests for that image size are faster.

This solution requires a bunch of work to be done on the server side, and I opted to go with a service to handle it all because they're also a CDN.

In the end the "Responsive Retina-fication" method you should choose depends greatly on your specific website and use of images. In most cases you'd be fine to use the code I walked through in my last post. But hopefully I've done a better job of explaining the alternatives and how things work in this post. Again, these methods are all just temporary and we'll soon be letting the browser do all the work natively.