Serving up responsive background images on the fly

Over the past 6 months or so, I’ve been redesigning my website. The design itself is pretty much final, but it’s an iterative process and there are always improvements to be made. One of my goals for the redesign was to cut down on bloat – both in terms of the CMS structure and users’ load time.

On the CMS side, I ditched WordPress in favour of Kirby. Its core files weigh in at 136 KB zipped, compared to WordPress’s 4.3 MB. Plus, Kirby uses a flat-file data structure that’s dead simple to install, backup, and modify.

But what I really want to get at with this post is my technique for serving up responsive images. As you can see on the homepage, there’s quite a large photo in the header, and I wanted to make sure that visitors get an appropriately-sized image for their device (nobody on a smartphone wants to download a 4000-pixel-wide behemoth).

I don’t claim to be an expert on this subject, and responsive image techniques have been tackled by a lot of people who are much smarter than me over the past few years. But this is a solution that’s easy to implement and gives appreciable results.

It’s important to note that the images I’m working with here are background images – so why don’t I just use media queries to serve up a responsive image? Well, because I have over a hundred of these images, and I plan to add more. They’re mostly travel photos, and I don’t want to manually create 3 or 4 different versions of each image. Your use case may require a different solution.

The code

Now that the caveats are out of the way, let’s get to it. I used the excellent TimThumb to do most of the grunt work. This method redirects requests for images through to a PHP script that will fetch the image, and scale and crop it accordingly. The implementation is simple:

// Load original image
<img src="/path/to/image.jpg" /> // Load resized & cropped image
<img src="/path/to/timthumb.php?src=/path/to/image.jpg&w=500&h=100&zc=1&q=75" />

timthumb.php accepts five variables:

  • src: absolute path to the image
  • w: width in pixels
  • h: height in pixels
  • zc: zoomcrop – possible values are 0 (no crop) and 1 (crop). (delault is 1)
  • q: image quality, out of 100 (default is 90)

My header area takes up the full screen width, so that should be the width of the image, too. As for the height, that depends on the size of the window because this is a responsive design. I’ll have to wait until my logo loads to measure the height of the header with javascript.

Here’s what it looks like. This script is placed right after the closing </header> tag:

<script> document.getElementById('logo').onload = function(){ // wait until #logo is finished loading var height = document.getElementById('header').offsetHeight; // measure the height of #header var width = window.outerWidth; // measure the width of the window document.getElementById("header").style.backgroundImage = "url(/path/to/timthumb.php?src=/path/to/image.jpg&h="+height+"&w="+width+")"; // set a background image to #header, using the height and width calculated above }

Of course, putting a plain CSS background-image style inside a <noscript> tag will ensure graceful degradation for those without javascript enabled. Just manually set the width and height to something middle-of-the-road.

The results

So what does this mean for performance? Let’s take a look at the results of two image downloads: the first is a full-size, 261 KB image of the Bastei bridge in Germany. The second is the same image, but resized to 1280px * 206px (this is the appropriate size for a header image on my 13” laptop screen).

Original Resized
File size 261 KB 71 KB
Latency 0.493 s 1.190 s
Total download time 10.78 s 6.96 s
Calculated download speed 24.2 kbps 10.2 kbps

I’m currently on a weak mobile internet connection, which is why these download speeds are so slow. But the main point here is that even though my connection speed was slower when I downloaded the resized image, it still completed the download faster. If the connection speeds had both been 24.2 kbps, the resized image would have downloaded in 2.93 seconds.

As you can see, the latency is higher when we resize an image, because TimThumb needs to do some server-side processing before sending data back to the browser. But the resulting file downloads more quickly, more than offsetting the initial delay.

That’s all there is to it! This solution is working for my header images at the moment, but the next step is to make all the images on this site responsive. Perhaps my best bet will be to extend this method; maybe I’ll need a parallel system; or maybe I’ll start from scratch with a comprehensive solution. Who knows? For now, I’m happy with yet another speed improvement.

Sam Nabi Sam Nabi