Responsive images with ExpressionEngine

So, as I mentioned in a previous post, this site is my first attempt at responsive web design (RWD). One area of this movement (can you call it that?) that has been a little trickier to solve is responsive images.

When it comes to images and responsive web design, the usual approach is to set max-width:100% on them so that they are only as large as the container around them allows them to be, which means they'll scale nicely regardless of the size of the screen you're viewing them on. The problem is that if you serve up a massive 3000-pixel wide panoramic landscape shot that looks great on your Apple Cinema Display, that same massive image is also going to be downloaded to someone using a mobile, except that it'll be scaled down to a few hundred pixels, using up that person's download quota for the month.

There's been a few different proposed solutions, e.g. by Scott Jehl, Nicolas Gallagher, Harry Roberts, and Keith Clark but I didn't feel any of them sat quite right with me so I thought I'd try a different approach.

What I came up with is specific to ExpressionEngine, but the concepts could probably be applied just as well to any other system where content and structure is manipulated server side before being sent to the browser, and it involves querying the device's user agent string to determine if it is a mobile or not and then setting a global variable which can then be used in templates to modify the size of the image output. So in other words, only one image gets sent to the browser, but that image is different depending on whether you're viewing the page on a mobile or desktop device.

So there's two parts to this technique:

  1. setting a global variable which I'm doing in a customised version of Leevi Graham's Config Bootstrap, and
  2. the actual templates which output the images.

The code I've added to my config is partly borrowed from Max Lazar's MX Mobile Detect plugin, and in fact the _mobileClients array he's using for the list of user agents comes from ZyTrax.

<?php

$default_global_vars['global:img_width'] = '';

$_mobileClients = array("android", "acer", "asus", "alcatel", "sie", "blackberry", "htc", "hp", "lg", "motorola", "nokia", "palm", "samsung", "sonyericsson", "zte", "mobile", "iphone", "ipod", "mini", "playstation", "docomo", "benq", "vodafone", "sharp", "kindle", "nexus", "windows phone","midp", "240x320", "netfront", "nokia", "panasonic", "portalmmm", "symbian", "mda", "mot-", "opera mini", "philips", "pocket pc", "sagem", "sda", "sgh-", "xda");

$mobile = false;

$userAgent = strtolower($_SERVER['HTTP_USER_AGENT']);
foreach ($_mobileClients as $mobileClient) {
    if (strstr($userAgent, $mobileClient)) {
        $mobile = true;
    }
}

if($mobile) $default_global_vars['global:img_width'] = '320';

?>

So for my site I've decided I'm only going to have two different image widths: mobile = 320px and everything else will be whatever the natural size of the image is scaled down to the window size by max-width: 100%. Mobiles will also get max-width: 100% so if their screen size is smaller than 320 the image will still be scaled down, but it's better to be scaled down from 320 to 240 than 500 to 240, for example.

And then in the template, I'm using the CE Image plugin to create smaller images based on an original source. (I tried originally to use the ImgSizer plugin but it seems that it doesn't like working with global variables as widths).

{exp:ce_img:pair src="{image}" max="{global:img_width}"}
	{ if global:img_width!=""}<a href="{image}">{/ if}
		<img alt="" src="{ if global:img_width!=""}{made}{ if:else}{image}{/ if}" alt="">
	{ if global:img_width!=""}{/ if}
{/exp:ce_img:pair}

So if global:img_width is not null it uses the CE Image resized image as the source, {made}, and if it is null, just uses the unprocessed image. I'm also checking if the global variable is null to wrap the image in an anchor to the full size image for mobiles if they want to get a larger view of the image.

I'm actually using this method on the site right now. If you view the source on a mobile, you should find some of the image file names appended with something like _320_XXX.

You could also extend this in various different ways. You could set different global width variables for different devices (although you'd need to get a bit more involved with your user agent headers checking as a lot of the manufacturers produce devices with different sized screens), or you could include extra images for the desktop that aren't included for mobile devices by checking if the global variable is null or not.

As I said earlier, I'm still quite new to this RWD game so it's quite likely there may be potential problems or pitfalls I've overlooked. So please let me know if I have or if you can think of ways that this might be improved.

Update (July 23): Max Lazar has pointed out that sie- in the _mobileClients array above will also target msie (Internet Explorer), so I've taken it out.

Update (August 12): Matt Wilcox has come up with another technique, Adaptive Images which I think is well worth a look. See the comments for a bit more information.