Detecting Support for data: URIs
By Weston Ruter
Updates: Added note at end to respond to V1’s comment, and fixed the “awkward CSS syntax” which was actually a big typo (thanks Harmen).
The data: URI scheme is now supported by the most current version of every major browser, including Internet Explorer. Because of this I wanted to use CSS background images encoded with data: URIs in a current project at Shepherd Interactive. Why? The first rule of High Performance Websites is to Minimize HTTP Requests. By storing background images directly in the stylesheet only one HTTP request is then necessary to fetch the stylesheet and the images all at once. Furthermore, by giving that stylesheet a far-future cache expiration date the browser will never need to request it again.
However, while every browser vendor’s current version supports data: URIs, older browsers like MSIE 7 do not. Therefore it is necessary to also have fallback CSS rules to serve externally-referenced background images. I came up a way of doing this by utilizing the following script which I place as an inline script in the document head:
var data = new Image();
data.onload = data.onerror = function(){
if(this.width != 1 || this.height != 1)
document.documentElement.className += " no-data-uri";
}
data.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
This script attempts to load a 1×1 pixel via a data: URI. If the onload callback fires and it detects that the image’s width and height are each a single pixel, then the browser supports data: URIs. Otherwise the browser does not support such URIs and a class name is added to the document root so that fallback CSS rules can be written, for example:
#foo {
background-image:url("data:image/png;base64,<...>");
}
html.no-data-uri #foo {
background-image:url("images/foo.png"); /* fallback */
}
In this way, CSS rules can define background images that utilize data: URIs only for browsers that support them, while at the same time older browsers still render the content properly due to the fallback rules. Note that for browsers that support data: URIs, the fallback CSS rule above will never be applied and thus its externally-referenced background image will never be downloaded. However, if the browser does not support data: URIs, then the resources will essentially be downloaded twice, but that’s the price you pay for using an old browser. In any case, when the images are tiny (<1KB) then the overhead is minimal for non-supporting browsers, and your development process is greatly eased since you don’t have to hassle with creating image sprites (which are sometimes impossible anyway); those who use current browser versions will be rewarded as will the generations of Internet users to come.
Comments
Wouldn’t this be utterly useless when it comes to performance? You are now loading the images 2x. One as normal and one as base64.
@V1:
If the browser supports
data:URIs, the external background images are never loaded because the fallback rules are never applied. My Firebug Net panel don’t show any of those images being loaded, and my YSlow is very happy.Clean and simple, thank you. It’s great that support for data:uri was added is Internet Explorer 8.
Can you elaborate a bit on that awkward CSS syntax?
Why repeat the background-image rule as a string inside the background-image rule?
Furthermore; a great solution, thanks. It would be nice to come up with a server-side solution which automagically writes data URIs to a stylesheet, since it’s obviously a bit of a hassle to manage such style rules.
[...] Ability to specify images as data inline in your style sheet. Typically used for small sprites in order to minimize HTTP requests, reducing overall load time. Now possible in all major browsers with the introduction of MSIE 8. Support for older browsers is available through Javascript. [...]
[...] not support Canvas, although IE8 does support the data: URI scheme, which is a prerequisite (see support detection method). When/if Gears’s Canvas API fully implements the HTML5 canvas specification, then this [...]
My only issue with data uris is that it means the image won’t be cached, which is normally what you want. When would using an embedded image be a better idea?
It’s very rare that an image will be requested only once.
@Rich:
I think they are useful inline if the images are small enough (i.e. a rounded corner) where there is an acceptable cost/benefit ratio of HTTP request count vs. page display time. But also I think that
data:URIs make the most sense in stylesheets. Actually,data:URIs in stylesheets are cached because the stylesheet itself should be cached. So when returning to the page, the stylesheet is loaded from the cache and thedata:URIs are loaded directly from it.That’s a very good point – I’d only really thought of them as inline for some reason. I agree, in fact, having thought about it more, almost any small image would be best served in this way.
Nice trick, however have you tried to find a way to detect the support non-asynchronously ?
I tried to use this technique not for css specifically but in conjonction with XHR progress event in order to display a real progress bar when loading heavy images (see my post in french, but code is readable : http://jpv.typepad.com/blog/2009/11/barre-de-chargement-dune-image.html)
However due to the various limitations of the browser around the length of data you can put in an URL, you can not load huge images (around 1Mo)
So this technique really should be confined to CSS images