CSS Images and Data URIs

data-uri-cssA few weeks ago I took a quick look at replacing the CSS images on this site with data URIs, via Nicholas Zakas’ handy CSSEmbed tool. I figured out I could serve the same amount of data with 1 HTTP request instead of 14. At face value it sounded like a huge performance gain: same amount of data transmitted, far fewer HTTP requests, how could it not result in significantly better performance?

That’s why the results I encountered when testing out the concept on a recent project were so surprising. Using data URIs for CSS background images actually slowed down the page.  Edit: To be clear, the negative performance effect I describe is particular to the conditions of my test, namely, a situation in which one or more normal CSS images are served alongside data URIs.  I’m not suggesting that using data URIs for CSS images results in poor performance in general.

The Plan

My specimen for the experiment was a small brochure site I’d recently designed. The original version required 20 HTTP requests with a total page load of 200K. The test version with the data URIs required 6 HTTP requests and had the same load size of 200K.

To implement the data URIs I set up three stylesheets:

  1. The original stylesheet, but with all the background image references removed;
  2. A stylesheet containing only the data URI CSS images;
  3. A stylesheet containing only the normal background image declarations.

The idea is that all browsers would get stylesheet #1, browsers that support data URIs (FF, Safari, Chrome, IE8, Opera) would get stylesheet #2, and IE6/7 would get stylesheet #3 with the normal image references.

So instead of trying to use MHTML or other trickery to coerce IE6/7 into reading data URIs, we simply decide that only the “modern browsers” get to play.

In addition to allowing us to work around non-compliant browsers, isolating the data URIs in their own file achieves two things. One, it keeps our main CSS clean and more maintainable. Two, it allows us to cache the images separately from the styles, so that we can make a simple edit to the main stylesheet without forcing users to re-download all of the image data.

The 6 HTTP requests for my test version consisted of 1 HTML, 2 JS, 2 CSS, and 1 background image (80K) that was too large to serve as a data URI. As we’ll see in a minute, this one image turns out to be a performance killer.

The Test

css-images-data-uris-waterfall

View Full-size

The first waterfall is the original page and the second is the page with the data URIs (both taken from Firefox 3.6 beta, empty cache). Even though the payload size is the same, the version with 14 more HTTP requests loads about 18% faster. We appear to be in violation of one of the laws of web performance physics.

If you have a lot of experience trying to make sense of these waterfall charts the problem should be immediately clear. In this example it’s not about load size or number of requests, but parallel downloads.

It appears that images referenced in a stylesheet are only downloaded by the browser once all stylesheets have finished downloading. (I don’t remember noticing this before, or it being mentioned in either of Souder’s books, but I’m assuming it’s a well-known fact.) Edit: For more on this, see the addendum at the end of the post.

This creates a problem if we have any images that are too large to serve as data URIs.  We can expect our data URI CSS file to be one of the larger page components, finishing its download late in the sequence even if initiated early. Any remaining CSS images, which will necessarily be on the larger size (32Kb+), will have to stand by waiting for the data URI CSS to finish downloading.

In this case, where we have only one of these large images, the image is downloaded all by itself, completely wasting the browser’s ability to download components concurrently.

Lessons (What’s an HTTP request optimizer to do?)

If you’re able to convert all of your background images to data URIs you’ll avoid the aforementioned trouble. But if you have even one CSS image that must be served regularly you’re likely to encounter a performance penalty. The degree of the penalty depends on the size of your data URI CSS file, the size of the remaining image(s), and how many other components your page contains. If the data URI file finishes up relatively early, while the browser is still downloading JS and inline images, the penalty will probably go unnoticed. If the data URI file finishes late, followed by a large image, then the negative effect is likely to be felt.

In some cases it may be possible to change a large background image to an inline image, enabling it to be downloaded independently of the stylesheets.

Final Thought

Looking at the above waterfall chart I began to wonder about an HTTP request sweet spot number, beneath which performance gains would be negated or even reversed. Modern browsers can easily handle 10 or more simultaneous requests. Are we working against the browser if we chunk the payload into 3 or 4 heavy files?

data URI Resources
Addendum

I whipped together a test page for looking at how browsers download images referenced in stylesheets. So far: In FF 3.5 and Safari 4, the browser will wait to start downloading CSS images until all external stylesheets have been downloaded, even if some of the stylesheets are placed at the bottom of the page. In IE7, the browser will wait only for stylesheets placed in the HEAD section.

Now that I think about it, this makes perfect sense. And I think it may have been touched on in High Performance Web Sites after all.

Both comments and pings are currently closed.

Discussion

I think you really should qualify your conclusion that “Using data URIs for CSS background images actually slowed down the page” in the second paragraph. That’s not a true statement when left standing on its own. What actually happened is that using data URIs in conjunction with one large-ish image *in this experiment* created a slower experience.

I’d advise trying a few more tests, across more browsers, as my experience shows that CSS images are actually downloaded not in relation to style sheets, necessarily, but rather in relation to when the style containing the image is used on the page. Thus, styles at the top of the page should have their images downloaded before styles at the bottom of the page (and unused styles containing images should not have their images downloaded at all).

Oh, and if you could add the filenames to your waterfall chart, that would really help the rest of us figure out exactly what’s going on.

You’re right. I didn’t intend to make a general statement about the performance effect of data URIs, but I was far from clear. I’ll make an edit for clarification.

The tests I ran were not exhaustive, but examining various sites in Firefox, IE7, and Safari I wasn’t able to find an instance where the downloading of a CSS image was initiated before the stylesheets had finished downloading. But I think this is consistent with what you said about CSS images being downloaded in the order in which they’re called in the HTML.

I’ve added a link for viewing the full waterfall chart with the filenames.

Thanks for your comments.

Hello, very interesting experiment. Can you put html code of your page? can you put online of test? Where place of your data-uri.css (head or body)? Thanks.

Thanks. I’ve set up examples of the original version and the data URI version. URLs below. It’s from a little site still in development. The data-uris.css file was placed in the head, immediately after the main stylesheet. Please let me know if you figure out anything I missed.

Original
https://lab.ravelrumba.com/datauris/org/

Data URIs
https://lab.ravelrumba.com/datauris/test/

Ok, I made few test of your page, and received new interesting results. I made timeout for response of data-uri.css.

I put general styles.css and data-uri.css in HEAD.
https://fullajax.ru/temp/datauri/ravelrumba/test_header.htm

Browsers Chrome 3.0.195.33, FF 3.5.5, IE 8.0.6001 and Safari 4.0.3 not renders a page while download of data-uri.css in process. But, Opera 9.62 renders a page immediately without waiting for end of download data-uri.css.

Next, I put general styles.css in HEAD and data-uri.css put in FOOTER (end of ).
https://fullajax.ru/temp/datauri/ravelrumba/test_footer.htm

Browsers Chrome 3.0.195.33, FF 3.5.5, Opera 9.62 and Safari 4.0.3 renders a page immediately without waiting for end of download data-uri.css. But, IE 8.0.6001 still not renders a page while download of data-uri.css in process.

Conclusions:
If your use data:uri css sprites allways put data-uri.css in footer, and need make “antifreeze” link for IE. In the near future I’ll make update online tool (duris.ru) in accordance with new results.

Thank you for the identification of problematic issues.

Does the placing stylesheet link in the footer is standard compliant? Maybe I’m wrong, but i don’t think so.

@Andrey
You’re correct. The HTML 4 spec says “Unlike A, it may only appear in the HEAD section of a document, although it may appear any number of times.” I’m not suggesting that people go against the standard, I’m just experimenting with different page conditions to see how the browsers react. =)