Adaptive Images for Responsive Designs

So you’ve been building some responsive designs and you’ve been working through your checklist of things to do:

  • You started with the content and designed around it, with mobile in mind first.
  • You’ve gone liquid and there’s nary a px value in sight; % is your weapon of choice now.
  • You’ve baked in a few media queries to adapt your layout and tweak your design at different window widths.
  • You’ve made your images scale to the container width using the fluid Image technique.
  • You’ve even done the same for your videos using a nifty bit of JavaScript.

You’ve done a good job so pat yourself on the back. But there’s still a problem and it’s as tricky as it is important: image resolutions.

HTML has an <img> problem

CSS is great at adapting a website design to different window sizes – it allows you not only to tweak layout but also to send rescaled versions of the design’s images. And you want to do that because, after all, a smartphone does not need a 1,900-pixel background image1.

HTML is less great. In the same way that you don’t want CSS background images to be larger than required, you don’t want that happening with <img>s either. A smartphone only needs a small image but desktop users need a large one. Unfortunately <img>s can’t adapt like CSS, so what do we do?

Well, you could just use a high resolution image and the fluid image technique would scale it down to fit the viewport; but that’s sending an image five or six times the file size that’s really needed, which makes it slow to download and unpleasant to use. Smartphones are pretty impressive devices – my ancient iPhone 3G is more powerful in every way than my first proper computer – but they’re still terribly slow in comparison to today’s desktop machines. Sending a massive image means it has to be manipulated in memory and redrawn as you scroll. You’ll find phones rapidly run out of RAM and slow to a crawl.

Well, OK. You went mobile first with everything else so why not put in mobile resolution <img>s too? Because even though mobile devices are rapidly gaining share in your analytics stats, they’re still not likely to be the major share of your user base. I don’t think desktop users would be happy with pokey little mobile resolution images, do you? What we need are adaptive images.

Adaptive image techniques

There are a number of possible solutions, each with pros and cons, and it’s not as simple to find a graceful solution as you might expect.

Your first thought might be to use JavaScript to trawl through the markup and rewrite the source attribute. That’ll get you the right end result, but it’ll have done it in a way you absolutely don’t want. That’s because of the way browsers load resources. It starts to load the HTML and builds the page on-the-fly; as soon as it finds an <img> element it immediately asks the server for that image. After the HTML has finished loading, the JavaScript will run, change the src attribute, and then the browser will request that new image too. Not instead of, but as well as. Not good: that’s added more bloat instead of cutting it.

Plain JavaScript is out then, which is a problem, because what other tools do we have to work with as web designers? Let’s ignore that for now and I’ll outline another issue with the concept of serving different resolution images for different window widths: a basic file management problem. To request a different image, that image has to exist on the server. How’s it going to get there? That’s not a trivial problem, especially if you have non-technical users that update content through a CMS. Let’s say you solve that – do you plan on a simple binary switch: big image|little image? Is that really efficient or future-proof? What happens if you have an archive of existing content that needs to behave this way? Can you apply such a solution to existing content or markup?

There’s a detailed round-up of potential techniques for solving the adaptive images problem over at the Cloud Four blog if you fancy a dig around exploring all the options currently available. But I’m here to show you what I think is the most flexible and easy to implement solution, so here we are.

Adaptive Images

Adaptive Images aims to mitigate most of the issues surrounding the problems of bringing the venerable <img> tag into the 21st century. And it works by leaving that tag completely alone – just add that desktop resolution image into the markup as you’ve been doing for years now. We’ll fix it using secret magic techniques and bottled pixie dreams. Well, fine: with one .htaccess file, one small PHP file and one line of JavaScript. But you’re killing the mystique with that kind of talk.

So, what does this solution do?

  • It allows <img>s to adapt to the same break points you use in your media queries, giving granular control in the same way you get with your CSS.
  • It installs on your server in five minutes or less and after that is automatic and you don’t need to do anything.
  • It generates its own rescaled images on the server and doesn’t require markup changes, so you can apply it to existing web content.
  • If you wish, it will make all of your images go mobile-first (just in case that’s what you want if JavaScript and cookies aren’t available).

Sound good? I hope so. Here’s what you do.

Setting up and rolling out

I’ll assume you have some basic server knowledge along with that wealth of front-end wisdom exploding out of your head: that you know not to overwrite any existing .htaccess file for example, and how to set file permissions on your server. Feeling up to it? Excellent.

  1. Download the latest version of Adaptive Images either from the website or from the GitHub repository.
  2. Upload the included .htaccess and adaptive-images.php files into the root folder of your website.
  3. Create a directory called ai-cache and make sure the server can write to it (CHMOD 755 should do it).
  4. Add the following line of JavaScript into the <head> of your site:
<script>document.cookie='resolution='+Math.max(screen.width,screen.height)+'; path=/‘;</script>

That’s it, unless you want to tweak the default settings. You likely do, but essentially you’re already up and running.

How it works

Adaptive Images does a number of things depending on the scenario the script has to handle, but here’s a basic overview of what it does when you load a page running it:

  1. A session cookie is written with the value of the visitor’s screen size in pixels.
  2. The HTML encounters an <img> tag and sends a request to the server for that image. It also sends the cookie, because that’s how browsers work.
  3. Apache sits on the server and receives the request for the image. Apache then has a look in the .htaccess file to see if there are any special instructions for files in the requested URL.
  4. There are! The .htaccess says “Hey, server! Any request you get for a JPG, GIF or PNG file just send to the adaptive-images.php file instead.”
  5. The PHP file then does some intelligent thinking which can cover a number of scenarios, but I’ll illustrate one path that can happen:

  • The PHP file looks for the cookie and finds out that the user has a maximum screen width of 480px.
  • The PHP has a look at the available media query sizes that were configured and decides which one matches the user’s device.
  • It then has a look inside the /ai-cache/480/ folder to see if a rescaled image already exists there.
  • We’ll pretend it doesn’t – the PHP then goes to the actual requested URI and finds that the original file does exist.
  • It has a look to see how wide that image is. If it’s already smaller than the user’s screen width it sends it along and stops there. But, let’s pretend the image is 1,000px wide.
  • The PHP then resizes the image and saves it into the /ai-cache/480 folder ready for the next time someone needs it.

It also does a few other things when needs arise, for example:

  • It sends images with a cache header field that tells proxies not to cache the image, while telling browsers they should. This avoids problems with proxy servers and network caching systems grabbing the wrong image and storing it.
  • It handles cases where there isn’t a cookie set, and you can choose whether to then send the mobile version or the largest configured media query size.
  • It compares timestamps between the source image and the generated cache image – to ensure that if the source image gets updated, the old cached file won’t be sent.

Customizing

There are a few options you can customize if you don’t like the default values. By looking in the PHP’s configuration section at the top of the file, you can:

  • Set the resolution breakpoints to match your media query break points.
  • Change the name and location of the ai-cache folder.
  • Change the quality level any generated JPG images are saved at.
  • Have it perform a subtle sharpen on rescaled images to help keep detail.
  • Toggle whether you want it to compare the files in the cache folder with the source ones or not.
  • Set how long the browser should cache the images for.
  • Switch between a mobile-first or desktop-first approach when a cookie isn’t found.

More importantly, you probably want to omit a few folders from the AI behaviour. You don’t need or want it resizing the images you’re using in your CSS, for example. That’s fine – just open up the .htaccess file and follow the instructions to list any directories you want AI to ignore. Or, if you’re a dab hand at RewriteRules you can remove the exclamation mark at the start of the rule and it’ll only apply AI behaviour to a given list of folders.

Caveats

As I mentioned, I think this is one of the most flexible, future-proof, retrofittable and easy to use solutions available today. But, there are problems with this approach as there are with all of the ones I’ve seen so far.

This is a PHP solution

I wish I was smarter and knew some fancy modern languages the cool kids discuss at parties, but I don’t. So, you need PHP on your server. That said, Adaptive Images has a Creative Commons licence2 and I would welcome anyone to contribute a port of the code3.

Content delivery networks

Adaptive Images relies on the server being able to: intercept requests for images; do some logic; and send one of a given number of responses. Content delivery networks are generally dumb caches, and they won’t allow that to happen. Adaptive Images will not work if you’re using a CDN to deliver your website.

A minor but interesting cookie issue.

As Yoav Weiss pointed out in his article Preloaders, cookies and race conditions, there is no way to guarantee that a cookie will be set before images are requested – even though the JavaScript that sets the cookie is loaded by the browser before it finds any <img> tags. That could mean images being requested without a cookie being available. Adaptive Images has a two-fold mechanism to avoid this being a problem:

  1. The $mobile_first toggle allows you to choose what to send to a browser if a cookie isn’t set. If FALSE then it will send the highest configured resolution; if TRUE it will send the lowest.
  2. Even if set to TRUE, Adaptive Images checks the User Agent String. If it discovers the user is on a desktop environment, it will override $mobile_first and set it to FALSE.

This means that if $mobile_first is set to TRUE and the user was unlucky (their browser didn’t write the cookie fast enough), mobile devices will be supplied with the smallest image, and desktop devices will get the largest.

The best way to get a cookie written is to use JavaScript as I’ve explained above, because it’s the fastest way. However, for those that want it, there is a JavaScript-free method which uses CSS and a bogus PHP ‘image’ to set the cookie. A word of caution: because it requests an external file, this method is slower than the JavaScript one, and it is very likely that the cookie won’t be set until after images have been requested.

The future

For today, this is a pretty good solution. It works, and as it doesn’t interfere with your markup or source material in any way, the process is non-destructive. If a future solution is superior, you can just remove the Adaptive Images files and you’re good to go – you’d never know AI had been there.

However, this isn’t really a long-term solution, not least because of the intermittent problem of the cookie and image request race condition. What we really need are a number of standardized ways to handle this in the future.

First, we could do with browsers sending far more information about the user’s environment along with each HTTP request (device size, connection speed, pixel density, etc.), because the way things work now is no longer fit for purpose. The web now is a much broader entity used on far more diverse devices than when these technologies were dreamed up, and we absolutely require the server to have better knowledge about device capabilities than is currently possible. Relying on cookies to do this job doesn’t cut it, and the User Agent String is a complete mess incapable of fulfilling the various purposes we are forced to hijack it for.

Secondly, we need a W3C-backed markup level solution to supply semantically different content at different resolutions, not just rescaled versions of the same content as Adaptive Images does.

I hope you’ve found this interesting and will find Adaptive Images useful.

Footnotes

1 While I’m talking about preventing smartphones from downloading resources they don’t need: you should be careful of your media query construction if you want to stop WebKit downloading all the images in all of the CSS files.

2 Adaptive Images has a very broad Creative Commons licence and I warmly welcome feedback and community contributions via the GitHub repository.

3 There is a ColdFusion port of an older version of Adaptive Images. I do not have anything to do with ported versions of Adaptive Images.

About the author

Matt Wilcox has been a web developer for seven years and spent the last six specialising in front-end coding and design at a couple of small agencies.
He loves bouldering, photography, and a good debate. He is continually re-starting his attempts to learn Japanese.


He has a personal website at mattwilcox.net and, after five years, a burning desire to get it re-designed.

More articles by Matt

Comments