A Look at How Browsers Download and Render CSS Background Images
Last week I wrote about using data URIs in stylesheets and how parallel downloads could be negatively affected in certain situations. The tests I ran got me thinking about background images, particularly how the browser downloads them in relation to the other components. After some Googling I was suprised to find very little information on the subject. There’s lots of great research by Yahoo, Google, Steve Souders, and others, on parallel downloads, parser blocking, placement of scripts and stylesheets, etc; but none of it seemed to touch on background images specifically.
What’s so special about background images? Well, I think we’ll find that in most modern sites and applications CSS background images account for a significant number (if not a majority) of the HTTP requests. Exceptions would be sites with heavy inline image content or sites with an aggressive approach to CSS sprites.
So to learn more about it I went out into the field, set up test pages for a few different scenarios, and ran them through a handful of browsers. Here’s what I found out.
How things usually work
First, let’s just review the usual scenario. If you’re following performance best practices you want to have your stylesheets in the HEAD so that they’re downloaded as soon as possible. The reason for this is that, in most cases, the browser waits to build the rendering tree until all stylesheets have been downloaded. The sooner you get the CSS downloaded, the sooner the browser can start painting the page.
Where images come in
Inline images are not blocked by stylesheets and can begin downloading as soon as they’re encountered in the HTML. CSS background images, on the other hand, are downloaded only when the referring stylesheet has finished downloading. CSS images are kicked off not in the order in which they appear in the CSS but in the order in which they’re called in the HTML. I’m not sure of the precise moment when the download is initiated, but my guess is that it happens just after the CSS rules have been matched, when the property values are assigned to the DOM elements. Maybe there are some browser engineers out there who could speak to this.
Depending on the browser and the conditions of the page, the exact manner in which the CSS images are handled varies. For example, in the aforementioned scenario where the stylesheets are placed in the HEAD, CSS images are not requested until all of the stylesheets have finished downloading (see Scenario 1 below).
On to the tests
Each test page includes two stylesheets. The first declares 10 background images. The second is set to sleep for 5 seconds. The second also sets the background color to a dark grey so we can see on the page when it’s finished downloading. Four page variations were tested in FF 3.5, Safari 4, IE7, IE8, and Chrome 3, using a stylish combination of Fiddler, Charles, and HttpWatch. All loads assume an empty cache.
Scenario 1, stylesheets in HEAD
As you can see, all of the images referenced in the first stylesheet have to wait for the second stylesheet to finish downloading. This is true of all five browsers I tested.
Note: In this scenario as well as the next, Chrome’s Developer Tools Resources timeline indicates that the images are not blocked by the second stylesheet. This is incorrect. I fell for it at first but upon checking in Fiddler it was clear that the requests for the images are not made until the second stylesheet has finished downloading. Nice try, Chrome.
Scenario 2, first stylesheet in HEAD, second stylesheet in footer
FF, Safari, and Chrome perform as they did in the first scenario. CSS image downloads are blocked until the large stylesheet in the footer finishes downloading.
IE7 and IE8 begin downloading the CSS images referenced in the first stylesheet once that stylesheet has been received. Note that even though the images are downloaded early the page is not rendered until the delayed footer stylesheet has finished downloading.
In all of the tested browsers the CSS images are kicked off as soon as the first stylesheet has finished downloading. No blocking occurs. Note the difference in the total transfer time.
What’s interesting is that in all of the browsers except IE8, the page is rendered immediately after the first stylesheet has been received. The external stylesheet seems to trip up the browser, causing it to construct the rendering tree well before the second stylesheet has been received.
As you can see by the background color in the test page, this causes the familiar FOUC (Flash of Unstyled Content) problem. But, at least from a visual standpoint, this double rendering doesn’t have to be a problem if the styles are coordinated correctly. For instance if you placed certain background images or styles for elements beneath the fold in your footer stylesheet.
Unfortunately, for uniformity’s sake, IE8 does not agree with the other browsers. It downloads the images early but displays the anti-progressive-rendering white screen until the second stylesheet finishes up.
What’s it all mean?
At this point I haven’t sussed out the extent of the real world significance, if any. I’m mostly just running experiments and posting results. If your site or application has lightweight CSS and some inline images, the performance implications of this are probably nil. On the other hand, if you’re working with large CSS files (as you might if you use data URIs for CSS images), it may be worth taking a look at how to get the CSS images kicked off earlier in the waterfall.
Have any thoughts? Please hit up the comment form below.