On Friday, we at Mahalo launched a revamped homepage and site-wide redesign, the result of months of technical and editorial development. I’d like to talk specifically about one new technical aspect of the homepage.
The old Mahalo homepage had, at any given moment, between 8 and 10 content images. (By “content images,” I’m referring to images that are not design elements, like backgrounds or logos.)
The new Mahalo homepage, which packs much more data into the same space, has at any given moment, between 75 and 80 content images. (This isn’t obvious from a screenshot due to the number of tabbed sections; for example, in the top left section, each of the five tabs contains 10 items, each of which usually contains an image.)
Displaying each of these images requires a separate request to the server, and according to some, each request can add about 0.2 seconds to the load time of the entire page; for 80 images, that’s 16 seconds of extra load time – unacceptable. Mahalo’s architecture is faster than the average site’s, but the general principle still holds true: more requests means longer load time and more stress on the servers.
To solve this problem, I was given the following tasks:
- Reduce the number of requests made for images when a user views the Mahalo homepage.
- Do so without affecting the productivity of the Guides, who upload the images and write the content for the homepage.
The solution for #1 is fairly obvious and is in widespread usage: CSS sprites. Using sprites means that all of the images for a given page are combined into a single image file, and then the browser displays sections of the master image in different parts of the page using CSS. If you dig around in the source of Mahalo’s index.html, you’ll see a reference to an image like this:
This image combines all of the separate images from the homepage into one, allowing the browser to download them all with a single request. Additionally, the total byte size of this single image is typically the same or smaller than the combined byte sizes of the images it contains, so it’s a win-win situation.
By setting the background-image CSS property of the images to this master image, we can then display only a section of the image by using CSS declarations like these:
#i-1 { background-position: -0px -230px; width: 149px; height: 115px; }
#i-2 { background-position: -0px -345px; width: 149px; height: 115px; }
#i-3 { background-position: -0px -460px; width: 149px; height: 115px; }
[...]
So problem #1: solved. Now, how to achieve this effect automatically, without affecting Guide productivity? Why, Python, of course!
Using a little Python and a touch of magic, we can retrieve the HTML for a given page, enumerate the images, retrieve the images, combine them into a single image, modify the HTML to strip references to the original images, generate the CSS necessary to implement sprites for the images, and save the HTML back out to the server. This allows our Guides to modify the homepage however they like, view their changes on a server that doesn’t use the sprites, and then sit back and enjoy as their changes are automatically published to the Mahalo production servers.
I don’t know of any other site of scale that is generating dynamic sprites, but the system we’ve set up has so far worked flawlessly. However, if you’re going to implement a similar system, there are a few caveats to keep in mind:
1. You need to be smart about how you stitch together your master image. Some poor planning (or none at all) can lead to a massive single image with tons of empty space, which will simply bloat the file size. Efficient use of space is key here.
2. By using sprites, any time a single image on the page changes, the user must re-download all of the other images, since they’re all contained in the same master image. Avoiding this problem might mean waiting for a certain number of images on the page to change before generating new sprites and HTML, or it might mean generating two or three master images for different sections of the page, so that master images for each section of the page can change independently of each other.
Have any of you ever implemented a similar system? What do you think of our implementation?
Neat stuff.