iPhone Web Optimization, Lesson 3: Requests and Images

So far, we've had a look at the properties of the iPhone's EDGE, GPRS or 3G link (noting that propagation delay is a worse bottleneck than bandwidth limitations, so getting the HTTP request count down is our top priority) and have talked about how to get the HTTP request count down for the textual part of a page (i.e. (X)HTML, CSS and Javascript).

By the way, in order to underline the gravity of propagation delay as a bottleneck rather than tight bandwidth, have a look at Anandtech's speed comparison of the original iPhone and the newly released iPhone 3G: Especially look at the Facebook example. Despite the 3G network having approximately 10 times the bandwidth of EDGE (at least in their testing scenario: This is network-dependent, of course), the iPhone-optimized Facebook page only loads slightly faster over 3G than EDGE. This is because bandwidth is not the bottleneck here. It's propagation delay.

Now back on topic: In this installment, we'll have a look at media data, which, in case of the iPhone, will mostly be images.

Quite often the majority of HTTP requests necessary for a page load is used to retrieve images, either being linked from your stylesheet or from image tags within your (X)HTML content. Thusly, your iPhone-optimized site can benefit greatly if you can get this request count down as much as possible.

But wait, isn't it good practice to slice up your images, serving many little pieces rather than one large image? The answer is that no, this used to be good practice back in the days when low-bandwidth internet access was the common case. Sliced images do not load faster, they just give the impression of doing so, because you see different parts of the image come up simultaneously. Over a high-bandwidth link with a moderate propagation delay, sliced images will actually load slower than the whole thing due to the request overhead. So in short: Don't do this for your iPhone content.

Let's have a look at two trivial techniques to reduce the amount of HTTP requests necessary to load your image data.

CSS Sprites
CSS sprites are an increasingly popular technique which is not only limited to iPhone-optimization, but also helps to speed up page loads on desktop computers. In short, it works by loading one big image that contains all the little image you need on various places of your page. In the stylesheet, you then use only this one image as an external reference and specify the "tile" you want to show up in a specific element by using the CSS background-position property.

So, for example, you might combine 5 10x10 pixel images into one 10x50 pixel image by stacking them vertically. You could then reference the first image by assigning the stacked image as background to a 10x10 pixel element and specifying a background-position property of 0. The second image would also be put into a 10x10 pixel element, but this time with background-position -10px, which causes the image to be "shifted up", so that the second image shows.

It's a very simple technique that can obviously help you reduce the HTTP request count a lot. I'm cutting the discussion a bit short here, since there are so many other resources on the web already. For example, css-tricks.com has a good overview of CSS sprites.

Data URLs
Data URIs (or actually URLs) are nothing more than base64-encoded binary content that is served inline within a text document, either an (X)HTML page or a stylesheet. You can use them wherever you'd usually use http URLs, e.g. in the src-attribute of an image tag, within an url() block in a stylesheet, etc. For more info, check out data URLs on Wikipedia.

Data URLs have the big advantage that you can use them to get completely rid of all external file (and image) references within your iPhone-optimized page, making it load blazingly fast, even over a slow GPRS link.

There are 4 main drawbacks, however: First, data URLs are not supported by Internet Explorer, even in version 7 (this might change with IE8), but this won't concern us much, since we're targeting the iPhone. It's only a huge reason not to use data URL in web content targeted at the desktop.

Second, data URLs are served inline, so if you reference the same image file multiple times within your document, you'll have to include it inline each time, wasting bandwidth to download the same data multiple times. However, you can easily circumvent this limitation by putting your image into a CSS class via the url() reference and then assigning this class to any element where you want the image to appear. This is good practice anyway. Thusly, this is not that much of a problem.

Third, base64-encoded data is approximately 33% larger than the same data in binary. For this reason, data URLified images should only be used for moderately sized images: If you need to display a large picture within your iPhone-optimized web content (such as a splash screen), where bandwidth becomes more of a bottleneck than propagation delay, it's better to accept the additional HTTP request and serve it as a "traditional" external image reference.

Fourth, they make developing your content slightly more inconvenient: Whenever you want to make a change to your data URLified content, you need to re-encode it and copy/paste it into your textual content again. This can quickly become rather annoying.

Therefore, I've written a little Perl script to help you alleviate this drawback. It's called dataUrlifyCss.pl and takes one required argument: The path to a CSS file. Optionally, you can pass a second argument, which will be the path to which the output is written. If you omit this second path, the script will print the results to stdout instead.

The script simply replaces all url() references within your CSS file with inline data URLs. Simply create your iPhone-optimized website with external image references in your CSS, then copy the whole filetree of the site to your disk and run the script to generate another CSS file from your original with all the external image references replaced by data URLs. You could even automate this (for example, when you deploy your content). Also, feel free to extend the script to data URLify src-attributes of image tags within an (X)HTML document too, or whatever other functionality you'd like. The basics are all there. If you're running MacOS X Leopard, you don't need to install any Perl libraries to run it (otherwise, make sure that the MIME::Base64 module is installed).

There is one more tiny issue that you'll have to deal with: Since the iPhone's processor is a lot slower than a typical desktop processor, decoding a data URL can take up to a second or so on the iPhone. Thusly, you will want to pre-decode data URLified images, especially those that need to be displayed interactively (such as on a "click" event).

To do this, simply create a div-element somewhere on your page and set the "visibility" attribute of the element's CSS style to "hidden". Do not set "display" to "none" instead, since this will cause MobileSafari to not compute the style for the element at all, preventing the decoding from happening. Alternatively, give the element a "margin-left" of "-1000px" or something, just to get it out of the way. Finally, put dummy elements with all the CSS classes that have data URLified images in them into this div-element. This will cause the iPhone to decode the data URLs for these CSS classes upon page load, so that the decoded images are already cached for when they are actually needed. If you don't do this pre-decoding, you might notice that it takes a while for an image to appear if it hasn't been used yet elsewhere in the page, and this is something you do not want.

You can see data URLs and the pre-decoding trick in action in my Quick ORF and VienNav iPhone web apps. Note that they both load with only a single HTTP request by incorporating both the data URL technique described here and the other request reduction techniques from the previous lesson. Try loading them on your iPhone over GPRS, EDGE or 3G: You'll notice that they load pretty fast even under less-than-stellar circumstances (e.g. where neither EDGE nor 3G is available, so that the iPhone falls back to the sluggish GPRS).

By combining the knowledge from these lessons, you can now build an iPhone web app that loads in a single HTTP request without introducing too much bandwidth overhead. Try it out with your own iPhone-optimized web content, and, believe me, you'll be stunned by how much faster it will load!

In the next lesson, we'll have a look at how you can test your iPhone-optimized web content under varying connection conditions such as a fast 3G link or a wonky, data-loss ridden GPRS link, all without even having to whip out your iPhone: You can do this right from the convenience of your Mac!

UPDATE: Next lesson now available

Previous iPhone Web Content Optimization Lessons
Lesson 1: The Thin Pipe
Lesson 2: The Dreaded Request Count

CSS Sprites on css-tricks.com
Data URIs on Wikipedia
Quick ORF