EJS logo

Better background images for responsive web design

Since publishing this yesterday, I’ve revised the post in response to many people saying that this is simply a stop-gap for browsers without background-size support. That’s true to a point, but the method proposed here offers several advantages to the CSS-only approach.

Responsive web design, we have a problem.

We apply percentage-based widths to our img elements to get fluid images and this is a very good thing. But what about background images? Support for background-size is pretty abysmal right now and still isn’t bulletproof, even when it is supported (I’ll get to that in a second), so we’ve been hacking our way around it, usually by using inline img elements and absolutely positioning them behind the content. This is all well and good, but at certain browser widths, it breaks.

And it breaks horribly.

The problem with inline images

I found this out the hard way while designing and building the Belong site (which we launched yesterday, by the way). Consider this:

Screenshot
Our absolutely-positioned inline img is doing fine as a faux background image, especially with the containing div’s overflow set to ‘hidden’.

And then this:

Screenshot
But look what happens when the browser window gets smaller: the image remains at 100% width and, in doing so, reveals the containing div’s background colour.

It’s true that you could attempt to avoid this situation by creating really tall background images, but that not only means massive files; it means you’d always have to ensure your content never gets too long. Impossible for dynamic sites.

There’s another problem, too: our faux background image has no idea where its own horizontal centre is. On the original JPG, I’ve placed a horizontal line at 300px from the top and 300px from the bottom to indicate its centre, but that line will almost never be in the centre of the element because the img displays from its top. Consider our example again:

Screenshot
The horizontal centre of the actual image — indicated by a white line on the original JPG — bears no relation to the horizontal centre of the containing element.

At this point you might be wondering if this really matters, and for some images, it doesn’t. However, for Belong, this was a major problem: it meant that — at some widths — there was no way you’d actually see the t-shirt design:

Screenshot
Poor ol’ Trent: completely cut off at wider browser widths

You can play with the problem demo page in the browser. Be sure to resize the window.

The problem with background-size

So they’re the problems with using inline img elements, but what about going for a CSS-only approach and using the experimental background-size property, as suggested by commenters below? It offers a key advantage in being able to detect its own centre (using background-position:centre centre ) and does quite well in most scenarios. However, it still breaks when a containing element become very narrow.

The reason for this is that width:100% is given priority. You could change it to height:100%, but then it would break at wider sizes instead. You could give both width and height a value of 100%, but then you’d lose proportion as the image fails to maintain its correct ratio. And the really key thing here is that you’d need to know your preference in advance, which just won’t work for dynamic content: add more text and suddenly your container’s height could be way more important than its width.

To see this action, play with this additional problem demo page in the browser. Again, be sure to resize the window to see the points at which this approach fails.

Correction: as pointed out by Chris Mousdale in the comments below, background-size does indeed work. Rather than using 100% or auto size values, be sure to use cover instead; e.g.:

div.div01, div.div02, div.div03 {
-webkit-background-size:cover;
-moz-background-size:cover;
background-size:cover;
}

A cross-platform solution

So what we need is a way for the image to fill both its width and its height at all times, while retaining proportion; like background-size:cover , but for all browsers. It shouldn’t matter if the container is wider than it is tall — or taller than it is wide — at any given point; it should be able to give priority to the appropriate dimension no matter how they’re changed by either the amount of content or the actual browser window size.

I was aware of a jQuery plugin called jQuery Backstretch, written by Scott Robbin, that does exactly this. It’s a fantastic script and I’d already used it on a client project, but the problem was that it only works on the body . I asked the Decode boys (the chaps behind Belong) if they might be able to modify the code so that it could works its magic on any element.

And they did. Say hello to jQuery Anystretch.

As well as being able to apply Anystretch to any block-level element, an additional refinement is that you can use multiple instances of it:

$('.div01').anystretch("img01.jpg");
$('.div02').anystretch("img02.jpg");
$('.div03').anystretch("img03.jpg");

Further advantages of using Anystretch include being able to change the background image on click, set a fade-in speed, and turn the whole thing into a gallery with just a couple of lines of code. I won’t go into all that here; you can find jQuery Anystretch on GitHub.

To fully appreciate the power of the script — and everything I’ve bleated on about in this post — be sure to check out the third demo that illustrates the Anystretch solution, or, for the best working example, check out wearyoubelong.com, which includes the gallery functionality.

All credit for this fork goes to Dan Millar of Decode and of course to Scott Robbin for the original jQuery Backstretch.

To the inevitable nay-sayers complaining about the use of Javascript for presentational elements: there are more important things in life.