This article is the final piece of a three part series investigating the performance of data URIs on mobile. You can check out my previous posts 'On Mobile, Data URIs are 6x Slower than Source Linking' and 'Data URI Performance: Don’t Blame it on Base64' for more information on the subject.
About a month ago, I did some research to understand why I was seeing poor performance using data URIs in a web component I was building. After posting my results, the most persistent question from the wider web performance community was:
"OK, but how do data URIs perform compared to CSS spriting as a technique to reduce the number of HTTP requests on a page?"
To provide an answer to this question, I’ve conducted further research on data URIs. In this article I’ll provide more background on why the performance of data URIs is an important issue, some additional details about the experiment and, of course, the results of whether data URIs or CSS sprites perform better on mobile.
A Quick Refresher on CSS Sprites
Why Spend So Much Time On Data URI Performance? Isn’t It A Minor Detail?
Not at all! As it turns out, the performance difference of using a data URI vs another technique can easily blow the 100ms interactivity budget that Jakob Nielson, one of the gurus of User Experience, described as the interval at which users perceive an interaction to have taken place instantly.
The performance of data URIs is important to any web designer or web application developer applying the best practice of minimizing HTTP requests. Data URIs are most often used for small graphics that persist throughout a site, but I know of no prior guidance that provides designers an upper limit on the size or number of resources that use data URIs in a page.
Looking at it from a wider context, we know that the Google Chrome team, the Apple Safari team, the Firefox team, WebKit developers and the Microsoft Internet Explorer are all doing an awesome job at building fast browsers for us to use — and being a web developer has never been better. Therefore it is essential that developers know the performance profiles of different techniques, so that they can apply them correctly.
While browsers provide an abstract environment that frees developers from implementation details, there are still many things developers need to know about browser behaviour — especially if the goal is to design mobile friendly websites that will render above the fold content in less than a second!
To answer the question of how data URIs compare with CSS spriting, I was interested both in how long it would take for first load (the uncached condition) as well as what ongoing performance difference might exist once the resources were cached by the browsers.
My goal for this experiment was to get as close to a real world scenario as possible. For this purpose I selected an actual sprite used by the largest e-commerce store on the internet: Amazon.com. This sprite is approximately 25KB in size and contains 39 separate images.
I created two blocks of HTML that would be loaded in as iframes. The first iframe contains CSS to specify the location of the sprite file as well as the offsets to layout each individual sprite as a background-image. The second iframe contains the inlined base64 encoded Data URI for the same image. I have provided links to the HTML used in the iframes as well as the sprite used in the test in the resources section at the bottom of this article. The HTML content was served using gzip compression.
The performance measurement began immediately before the iframe is instantiated (when the src= attribute is assigned) and ended when the iframe load event was fired. Since smartphones running iOS do not have the navigation timing API, timing resolution was limited to the millisecond accuracy of the date object, which is more than adequate for this test given the large spread in performance.
This experiment tests cached versus uncached behaviour for data URIs and sprites, giving a total of four conditions. Each condition was executed in an independent iframe. Data URI and CSS sprite conditions were randomly assigned, but the cached condition test always took place immediately after the uncached condition to take advantage of work already completed. This was done using a separate iframe with the parent window remaining constant between cached and uncached conditions. The following cache-control header was used on all resources including the iframe HTML content for all conditions and the CSS sprite:
Cache-Control: public, max-age=2592000
In total 2.8 million samples were collected for this analysis.
Results & Conclusion
It’s interesting to note that CSS sprites outperform data URIs by several hundred milliseconds in the uncached condition across all browsers. This is in spite of the fact that the CSS sprite actually requires an extra connection and incurs all the associated connection overhead including TCP slow start!
We can see from the results that in the cached condition for both Android 4.2 and iOS 6 that sprites offer roughly 2X performance with a difference of around 220ms for iOS and 70ms for the Android 4.2 native browser. Chrome on Android and Firefox fare a bit better, with about a 50% or ~60 ms performance difference in favor of CSS sprites.
Bearing in mind this is for only a 25kB sprite, you can see a significant performance difference between the two conditions. Generally, performance will be a function of the size of the collected sprite image in addition to a fixed cost for instantiating each separate image. If you move large images or scripts to data URIs, the performance impact is significant.
Based on this research, I recommend limiting the use of data URIs to very small resources and to not use too many of them in your CSS or inline HTML. 15-20kB for max data URI size, and no more than 3 - 5 instances seems like a good rule of thumb for mobile!