QR Code contains TinyURL of this article.Simple Responsive, Adaptive Images with Support for Retina Devices

Aurora borealis above Lyngenfjorden
Aurora borealis above LyngenfjordenCredit: . License: CC BY-SA 3.0

Take a look at the photo above. Beautiful isn’t it? The photo is retina-friendly, responsive and adaptive. Yet it’s just 84‍KB in size1 and I only had to produce a single image to meet all three buzzwords.

How did I do it?

First of all, let’s look at what those buzzwords mean:

Retina-friendly
Retina is simply Apple marketing speak for a high pixel density. A traditional computer display has a pixel density of ‍72‍PPI. A retina display has a minimum pixel density of ‍160‍PPI, more than double that of the norm. Retina displays thus render text and images with outstanding clarity and, theoretically, are of a high enough pixel density that the human eye cannot discern individual pixels at normal viewing distances.
As there are more than twice the number of pixels per inch to fill, we need to make our images available with dimensions at least twice those that we want to display them at. For example: if I intend to display an image at 640×480 pixels I will have to save that image at 1280×960 pixels. If I don’t, then the computer will simply upscale my 640×480 image to 1280×960… and upscaling sucks, producing artifacts, blurring and overall image degradation.
Therefore, in order to best serve both users of traditional and retina displays, conventional wisdom suggests that we need to make available both regular and @2x2 versions of each image.
Dramatic makeup
GeishaCredit: . License: CC BY 2.0
Responsive
A responsive image (or website) is one that reacts to changes in the viewport dimensions and/or aspect. View a responsive website page on a cellphone, tablet or desktop PC; view it on a 4″ or a 40″ display; view it in a landscape or portrait orientation… the content will dynamically reshape itself to fit.3
Adaptive
Responsive images are all well and good, but just dynamically scaling an image to a small viewport is wasteful of expensive and often limited bandwidth. A 1027×768 responsive image, at say 200‍KB, is still a 200‍KB download even if one is viewing it on a 4″ display. It is more efficient to serve a smaller image (in both dimensions and filesize) to a smaller display.
Ideally then, we should serve small, light-weight images to cellular phones, medium-sized images to tablets and the full-sized images only to those devices with sufficient screen real-estate (and bandwidth).

There are a couple of problems that we need to address in order to serve responsive, retina-friendly, adaptive images then:

  • We potentially need to create and manage 6 files for each image we want to display: regular, retina, small, medium, large (original) and thumbnail;
  • We need to determine which of those variants we should serve to satisfy an image request.
B&W photo of a derelict copper mine building in Somerset, England
Derelict copper mine building near Dodington, Somerset in EnglandCredit: . License: CC BY-SA 2.0

The Wrong Way

I have noticed the following retina-handling methods in the wild:

№ 1
Serve a non-retina image by default, then use JavaScript to determine whether or not the client is using a retina display. If she is, then replace the standard image with its @2x counterpart.
This solution works well enough, but is expensive as the client downloads two images instead of one on retina devices.4  There is also the possibility that the user will see the image switch, which could be somewhat jarring. Furthermore, if JavaScript isn’t available or fails, the browser will fall-back to the regular image on all devices.
I don’t consider this to be a worthwhile methodology. I am reluctant to have any kind of JavaScript dependency as I consider it to be inherently fragile.
№ 2
An optimised version of the above: serve a single-pixel transparent GIF as the default image, then JavaScript replaces that with either the regular or retina version of the actual image once it has determined the display pixel-density.
This works well and is less bandwidth hungry than the first solution. However, if JavaScript isn’t available or fails, then no images will be visible as the browser will display only the transparent GIF‍s.
I would never use this. The possibility of the client getting no images at all (apart from the transparent GIF) is not something I want to risk.
№ 3
Use CSS Media Queries — targeted on min-device-pixel-ratio — to change a placeholder element’s background image.
I dislike this method. It’s semantically incorrect to use a background image in the place of an <img> element. However, this solution does not rely on fragile JavaScript and is thus more robust.
№ 4
Use the srcset attribute with a JavaScript polyfill for those browsers that don’t support it.
This solution provides for art direction too and that’s a highly desirable characteristic for responsive design.
I’m convinced that the <picture> element and srcset attribute is how we’ll ultimately deal with responsive and retina images but, urgh, that syntax, and all that file management! Really?
<picture>
  <source sizes="(max-width: 30em) 100vw, (max-width: 50em) 50vw, calc(33vw - 100px)" srcset="pic100.jpg 100w, pic200.jpg 200w, pic400.jpg 400w, pic800.jpg 800w, pic1600.jpg 1600w, pic3200.jpg 3200w" />
  <img src="pic400.jpg" alt="Argh, this syntax is horrible." />
</picture>
Mt. Everest
Mt. Everest North FaceCredit: . License: CC BY-SA 3.0

One Image to Rule Them All

As a lazy programmer, I want to simplify things as much as possible. Why should I have to create and manage half-a-dozen images when I can manage just one and automate the production of the rest?

So, using the Mt. Everest image above as an example. Let’s examine how we deliver retina-friendly, responsive and adaptive images from a single source image.

We start with a retina-resolution image (1640×646 pixels), which we save with a low JPEG quality setting5 to keep the file size down. We then request the image with standard PPI width and height values. The browser displays the image at the designated dimensions but has the extra detail required for retina displays.

The markup looks like this:

<figure itemscope style="width: 820px;">
  <img alt="Mt. Everest" src="/assets/images/north_face_of_everest_tibet_china@2x.jpg" width="820" height="323" itemprop="work" />
  <figcaption>
    <strong itemprop="title">Mt. Everest North Face</strong> &#8212;
    <span>Credit: <span itemprop="author">Georgios Giannopoulos</span>.</span>
    <span>License: <a href="http://creativecommons.org/licenses/by-sa/3.0/deed.en" itemprop="license">CC BY-SA 3.0</a></span>
  </figcaption>
</figure>

Notice how we halve the image dimensions for all displays, hence the: width="820px" height="323px" attributes on the img tag.6  That’s the high-PPI displays taken care of.

In our SCSS we have the following magical directives which make our images responsive:

figure {
  max-width: 100%;
  img {
    max-width: 100%;
    height: auto;
  }
}

Which brings us to the adaptive images. For this we use the adaptive-images.php script. This essential little script detects our visitor’s screen size and automatically creates, caches and delivers device appropriate re-scaled versions of our images.

Configuration for adaptive-images.php is in two parts. The first, in the <head> of our HTML:

<script type="text/javascript">document.cookie = 'resolution=' + Math.max(screen.width,screen.height) + ("devicePixelRatio" in window ? "," + devicePixelRatio : ",1") + '; path=/';</script>

followed by some URL rewriting directives in our httpd.conf or .htaccess file:

RewriteEngine on
# BEGIN: Adaptive-Images
# Add any directories you wish to omit from the Adaptive-Images process on a new line, as follows:
# RewriteCond %{REQUEST_URI} !ignore-this-directory
# RewriteCond %{REQUEST_URI} !and-ignore-this-directory-too

# Don't apply the AI behaviour to images inside AI's cache folder:
RewriteCond %{REQUEST_URI} !ai-cache

# Send any GIF, JPG, or PNG request that IS NOT stored inside one of the above directories to adaptive-images.php so we can select appropriately sized versions
RewriteRule \.(?:jpe?g|gif|png)$ /adaptive-images.php
# END: Adaptive-Images

Minimal effort, maximum gain. Perfect!

See Also

Don’t miss the follow-up article: My U-turn On Responsive Images

  1. 84KB download to retina desktops; 61KB to non-retina desktops and just 36KB to a 4″ retina smart-phone. ↩︎

  2. @2x has become the de-facto standard filename suffix for high PPI images. ↩︎

  3. You can see the effect on this website by resizing the browser window, or viewing on a tablet or cellular phone. ↩︎

  4. Which is especially bad for mobile users who have capped data allowances. ↩︎

  5. Sometimes as low as 10% but generally ~30%, depending on the image. ↩︎

  6. The width and height attributes also tell the browser what space to allocate for the images as it paints the display. Without them, the browser isn’t able to provide a placeholder while it waits for the images to download. This means that the content collapses to fill that space then, when the image downloads complete, the browser has to repaint to accommodate them. The result of this is that the content appears to jump about while a page loads. When the browser knows what the image dimensions are beforehand, as it does when we specify them, then it leaves a space matching those dimensions as it renders the content. Then, when the downloads are complete, the images simply drop into the space the browser allocated for them. The result: no jumping content and a more pleasant experience for the user. ↩︎