highsrc - Simple and Effective
High-DPI "Retina" Images
Even if you're on the right track, you'll
get run over if you just sit there.
With the introduction of the high-DPI "retina" screens of Apple's iPhone 4 and iPad 3, and with similarly impressive screens sure to quickly follow on all laptops, desktops and other computing devices, there is a clear and immediate need to support high-resolution images on the web. This has recently engendered much discussion about how best to do so. Temporarily, companies like Apple are using JavaScript-based approaches to swap in high-DPI images, but in the long term we need a proper, standardized solution.
One popular proposal is the introduction of a new <picture>
tag into HTML, with nested <source>
tags inside it pointing to multiple alternative image files from which the browser will choose the most suitable one, modeled after the same system used for the <video>
tag in HTML5. A traditional <img>
tag inside the <picture>
tag would serve as a fallback for old browsers, just as with the <video>
tag...
<picture id="picbob" alt="Photo of Bob"> <!-- width and height of #picbob must be set in CSS --> <source src="bob_2x.jpg" media="(min-device-pixel-ratio: 1.1)"> <source src="bob.jpg"> <img src="bob.jpg" width="95" height="63" alt="Photo of Bob"> </picture>
A competing proposal argues for a new image file format which can hold multiple different versions of an image in a single file, along the lines of the Mac's .icns or Windows' .ico formats used for application and document icons, but designed in such a way that a web server could send just one part of the file to the browser, using some form of content negotiation. As a fallback for old browsers, the web server could send a PNG-converted version (perhaps created dynamically on-the-fly, perhaps also embedded inside the single file), which means the approach wouldn't work for old browsers when viewing local files on disk, rather than over the network...
<img src="bob.webimage" width="95" height="63" alt="Photo of Bob">
Instead of those approaches, I propose a new highsrc
attribute for the good old <img>
tag...
<img src="bob.jpg" highsrc="bob_2x.jpg" width="95" height="63" alt="Photo of Bob">
Note that the filename for the high-DPI version could be anything you like, I just happen to use Apple's recommended naming scheme in the above example. Also note that the width and height remain unchanged, and still use CSS pixels as their units. Finally, note that the high-DPI version need not actually be exactly 2x the resolution – the browser will resize it to fit the specified size, just as with any other image with a specified size. Naturally, this approach would be fully compatible with old browsers, which would simply ignore the highsrc
attribute, just like any other attribute they don't understand.
Why use an attribute, rather than a new HTML tag or a new image file format? Well first, it's been done before and worked well – in the early days of the web, when Internet links were much slower than they are today, there was a lowsrc
attribute for the <img>
tag, which browsers would load and display first, before the normal image set in the src
attribute. Web designers could set lowsrc
to a lower-resolution or lower-quality version of their images, especially big ones, to make their pages appear to load faster on slow dial-up modem links. And browsers could automatically use, or choose to ignore, the lowsrc
attribute, depending on the expected link speed (perhaps configured in a browser setting).
Even thinking about adopting a new image file format is pretty unrealistic. As Forest Tanaka and Michael Whyte pointed out, we're still using iepngfix to have proper transparency support in IE6, 17 years after PNG was designed. New image file format adoption and debugging by browsers and graphics tools is just way too slow for something as important and time-critical as this. How many of us are reading this very article on an iPad 3 already, hmmm? We need a standardized solution NOW!
A new image file format with multiple versions in one file also doesn't mesh well with caching (at the browser, transparent proxy in the network path, and at the server end with CDNs), and caching is of utmost importance for performance. Simple, effective caching is everything.
In the long term, the proposed <picture>
tag is probably worth having and provides a nice, general solution to the core problem, assuming the browser is smart enough to not choose an insanely large version on a very slow link, and hopefully gives the user some kind of control over that decision in its settings. I'm all for <picture>
but...
<picture>
is very verbose for something as common as images. That verbosity, and the resulting flexibility, is great for <video>
because you're unlikely to have more than one on a page, and you can see in many cases the flexibility will be useful, even necessary, due to the wide variety of different video file formats and codecs in existence even today, let alone in the future. But images are way more common than video, so brevity and simplicity matter much more. And we want compatibility with all those scripts out there which read or manipulate <img>
elements. And, most important of all, we know the overwhelmingly most common case for images will be just 2 versions – a normal version and a high-DPI version at 2x resolution. That's going to account for 99% of all uses.
So why not just keep it simple, by adding that common case directly to the good old <img>
tag, as a new attribute called highsrc
, with behavior defined to be:
- The browser may, at any time, choose to set
src
to the value ofhighsrc
. That would usually happen during initial page loading on high-DPI systems, unless the user turned it off in the browser's settings. But it might also happen after loading the initialsrc
images, if the link is slowish but not too slow, where it's worth loading the normal images first then replacing in the background as the high-DPI ones come in during idle time. The browser could choose to do that when it thought it would give a better user experience (say, if the page is coming in a bit too slowly), or it could make it another setting which the user could control ("load low-resolution images first"). - If JavaScript code sets
src
, that implicitly also setshighsrc
to the same value. JavaScript would need to sethighsrc
explicitly after settingsrc
when doing something like a high-DPI rollover, to be safe and simple, and to not break any existing scripts, while allowing easy adoption of the new attribute. By havingsrc
also sethighsrc
, all existing scripts that manipulate<img>
tags are guaranteed to work perfectly with no changes, and can be incrementally updated to also explicitly sethighsrc
to an appropriate value in time, if desired.
I can't see anything obviously problematic with that approach myself. It declares the versions available in the HTML in a simple, brief way, then it gives the browser the decision, which seems to be the right place. That means the user can have an input through the browser's settings, and the current link speed can be taken into account too (by the browser). So if a user is paying per MB he can turn high-DPI images off, just like he can turn off all images in most browsers today. And on a slow link the browser can adapt, either automatically or with some kind of user setting to help – personally I would provide a simple setting like "don't load high-DPI images that will take more than X seconds".
Simple. Easy to understand (and teach). Easy to implement quickly in browsers today. Easy to standardize. Doesn't break any existing pages or code.
During the transition period, until browsers supported the new highsrc
attribute natively, a simple JavaScript polyfill can provide the same highsrc
functionality. It only takes 12 lines...
if (window.devicePixelRatio > 1 && !("highsrc" in new Image())) { document.addEventListener("DOMContentLoaded", function() { var images = document.getElementsByTagName("img"), imagesLength = images.length, i, highsrc; for (i = 0; i < imagesLength; i++) { highsrc = images[i].getAttribute("highsrc"); if (highsrc) { images[i].src = highsrc; } } }, false); }
...or even fewer lines using a library like jQuery...
if (window.devicePixelRatio > 1 && !("highsrc" in new Image())) { $(document).ready(function() { jQuery("img[highsrc]").each(function() { $(this).attr("src", $(this).attr("highsrc")); }); }); }
PS: Of course, adding an attribute to the <img>
tag doesn't tackle the problem of high-DPI images in CSS (backgrounds and sprites), but for that it's fairly clear some kind of @media
query is the natural approach.