Categories
Technology

Detecting Support for data: URIs

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.

21 replies on “Detecting Support for data: URIs”

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.

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.

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 the data: 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

Bit surprised by the “that’s the price you pay for using an old browser”. Why not add an “else”?

var data = new Image();
data.onload = data.onerror = function(){
	if(this.width != 1 || this.height != 1)
		document.documentElement.className += " no-data-uri";
	else
		document.documentElement.className += " data-uri";
}
data.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";

And the corresponding rule update:

html.data-uri #foo {
    background-image:url("data:image/png;base64,...");
}
html.no-data-uri #foo {
    background-image:url("images/foo.png"); /* fallback */
}

Now each does what it should, and no-one is penalized (assuming the same loading behaviour as described in the post, I haven’t checked it yet).

BTW, I have found that IE, including 9, has inconsistent behaviour with data uris. If used for an image the block of data can be formatted into lines using “\”, but if used for background images the load will fail. Another browser did this too … looks like we have to make it a single line to be cross-browser; could this be the reason for IE’s small data uri length limit?

Also, I couldn’t get the smaller image mentioned in the comments to load in IE9, though it worked in Firefox; it caused onerror to trigger with strange width and height.

Nice article. Thanks. (Just what I was looking for =)

@PVHL:
Thanks for the feedback, especially with the finding that the smaller GIF provided by Mathias doesn’t work in IE9.

Bit surprised by the “that’s the price you pay for using an old browser”. Why not add an “else”?

I don’t see the benefit in your code over mine as far as the price of having to download the image twice if using a browser that doesn’t support data: URLs. In both of our code, the Base64 image is downloaded along with the stylesheet regardless if the browser can use it, and in both cases the non-supporting browser will have to download the fallback separately. So isn’t the result the same? And actually, I think your code results in an even more undesirable situation: it requires that the user have scripting enabled in order to see any background image at all. In mine, the only case where they wouldn’t see a background image is if they are using a non-supporting browser and they have scripting turned off. But with your code, they won’t see a background image in any browser that has scripting disabled.

Am I overlooking something? Thanks!

Absolutely. You’re overlooking that a) I wasn’t paying enough attention, and b) I wasn’t paying enough attention. Right. Already downloaded with the CSS … my apologies. Where’s the delete button when you need it?

Anyway, I applied your method to simply turning on/off text and images in IE6/7 and it worked well, but I thought it was easier with the two class possibilities. (Obviously, star hack accomplishes this too, but I was looking for a more elegant and standards compliant method.) Going back now and looking at it again I see that only one is ever needed, and as you say, in a scripting disabled scenario this would be much better (my application is for a javascript required application). Long day? Senility? Was I awake? I don’t know.

Still, it did get me to check out your other work and discover OpenScripture which I look forward to looking into further.

Thanks for the feedback.

Hi.

Is there an analogous method for detecting if support for data uri’s exist where the content is CSV – something like – data:application/csv;charset=utf-8,a,b,c

I’m trying to offer a client a download option for some data.

The above will not work on IE so I have a server-side workaround, but it involves posting (lots) of data back to the server.

I would rather offer the workaround only to those browsers that really need it.

Thanks.

You mean to detect if the user’s browser has support for the CSV format? I think what you’re actually needing to look for is what they’ve sent in their Accept HTTP request header, to see if it is mentioned. But I doubt it would be, even if the user has an app installed which supports it.

Or if you really are just wanting to check if their browser supports CSV, then you can continue to use the sample image. You have to use an image because the detection relies on the load event getting fired.

What i mean is the browser may display some tabular data. The user is given the option to save the data as a CSV file. With many browsers I can do this with the “data:application/csv” uri and not have to go to the server again. However this is not supported with IE – so in that case I get the data from the server as a csv file.

What I cannot do easily/reliably is detect the browser.

This is why a test like your image code above would be ideal.

Thanks

Why not just use this?

#foo {
    background-image:url("images/foo.png");
    background-image:url("data:image/png;base64,");
}

This approach relies on the browser’s CSS parser to not accept the second background-image property as being a syntax error. However, the property itself is valid, as is the property’s url() value. I recall when testing in IE, the latter would result in an HTTP request to a URL like: http://example.org/css/data:image/png;base64, indicating the first background-image was overridden and the data: URL was interpreted as a relative path. This is what I recall noticing 5 years ago.

Leave a Reply

Your email address will not be published. Required fields are marked *