Thu 17 June 2010

You got your Base64 in my CSS!

If you're a developer working on a website that's getting any traffic, you'll inevitably come up against the problem of making sure you're as performant as possible on the front-end. You'll probably compress your Javascript and CSS, maybe refine the slower portions of your site after profiling them, and maybe (if you're smart) sprite your images.

You see, one of the chief goals in terms of a highly performant site is to get the amount of HTTP requests as low as possible. Most people turn to CSS sprites; they're a great technique for designers, and fairly easy to understand. That said, they're not without their downsides. They can become quite an unmaintainable mess if you're not careful - change the location of a few images, and you have to change the corresponding CSS declaration to match it. On a large site, you'll end up repeatedly putting together a puzzle that's wasting your time.

What if you're working on a team? How do you manage to keep one image in sync across 2 people? 5 people? At this point, you're probably wondering why I'm even bothering to rant on this, since these points are all fairly well known. Well, here's the thing - there's an alternate solution, depending on how open to the concept you are.

What the hell is Base64?

Just about every modern browser supports the concept of Data URI Schemes. With this, you can do the following:

body {
    background: transparent url(data:image/png;base64,
iVBORw0KGgoAAAANSUhEUgAAAAEAAABnCAIAAABO2r+ZAAAA
GXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAA
AG9JREFUeNp8T9sNgDAIvJCu4Ay6rEO5hXEBP9yB0xLa0Fr9
aLgHBxTrdgpIASB8HoiAXf/VaJmSbbj1df1jnpyn1qfViF+e
31T3Rxw8yzSez4u4u89medWMtWiZB197jYP8t1dv878u85T2
47oFGAC4z6tdlRsLNAAAAABJRU5ErkJggg==) repeat-x top left;
}

A bit ugly? Yeah, you might say so. Effective? Definitely. The above code simply generates a blue gradient that tiles perfectly. The really long block of text you see there is a Base64 encoded string that essentially gets transformed into your image. There's various different ways to generate the Base64 representation you need; if you want a really simple tool, check out this Firefox extension.

The format of a Data URI is fairly simple to understand - in this case, we're saying that this is a PNG, getting passed via Base64, and then the Base64 string itself. If it helps, think of it as a glorified function call.

As I noted above, most modern browsers support this technique. The one caveat worth mentioning is that Internet Explorer 8 is limited on the size of a Data URI to around 32kb. It's not really recommended to use this technique for anything above that size, but hey, your life, do what you want.

Oh, and Internet Explorer 6 and 7 have absolute zero support for this method.

Don't fret, we can make Base64 encoding work in IE6/7!

Internet Explorer (Explorer in general, really) supports another file type that does understand Base64: mhtml. We can use this trick to make IE6/7 do some awesome magic:

/*
Content-Type: multipart/related; boundary="_"
--_
Content-Location:1
Content-Type: image/png
Content-Transfer-Encoding:base64
iVBORw0KGgoAAAANSUhEUgAAAAEAAABnCAIAAABO2r+ZAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAG9JREFUeNp8T9sNgDAIvJCu4Ay6rEO5hXEBP9yB0xLa0Fr9aLgHBxTrdgpIASB8HoiAXf/VaJmSbbj1df1jnpyn1qfViF+e31T3Rxw8yzSez4u4u89medWMtWiZB197jYP8t1dv878u85T247oFGAC4z6tdlRsLNAAAAABJRU5ErkJggg==
--_--
*/

body {
    background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAABnCAIAAABO2r+ZAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAG9JREFUeNp8T9sNgDAIvJCu4Ay6rEO5hXEBP9yB0xLa0Fr9aLgHBxTrdgpIASB8HoiAXf/VaJmSbbj1df1jnpyn1qfViF+e31T3Rxw8yzSez4u4u89medWMtWiZB197jYP8t1dv878u85T247oFGAC4z6tdlRsLNAAAAABJRU5ErkJggg==) repeat-x top left;
    *background: transparent url('mhtml:http://absolute_url.com/css_file.css!1') repeat-x top left;
}

What we're doing here is fairly simple, but hilariously hacky. Take a look at the top of the file - it's a damn MIME header, with a little "pointer" (content-location) to our Base64 representation. We then just do a second background declaration for IE only that points to the same stylesheet, but parses it as mhtml instead. Our !1 is simply an identifier pointing to our first declaration - subsequent encodings could use 2, 3, "jackalope", whatever you want. Each "block" is defined by a "separator" or "boundary" - in this case, we're using "_".

I stumbled across this little trick while working on performance for Webs.com. However, it seems I can't take credit for it - Stoyan Stefanov posted on this awhile back. However, finding his work led me to one barrier that I've yet to see a solution to.

IE7 on Windows Vista - oh god, why?

It would seem that, due to some of the security settings on Vista (and maybe Windows 7), IE7 refuses to load the necessary styles. Stefanov advocates making the browser always request the mhtml as a new file. This is counter to the goal, though - we want that file cached.

Well, I happened to stumble on an easier solution (a combination of reading the MHTML spec and rearranging formats out of madness). Just make sure to end your IE hackery on a boundary declaration - the IE code above already does this: notice the "--_--" right before the end of the comment containing all the Base64 encodings for IE. This'll have IE7 parsing correctly everywhere, and we can all sleep soundly at night.

Whew! Awesome, now what?

Well, that's for you to decide. This technique can be quite useful - at Webs.com, I was able to cut the size of our sprite down immensely by relying on this trick for all the background tiling gradients on the page. In my humble opinion, this trick is good, but it works best when you combine it with a CSS sprite. I've found the best method is to split the usage down the middle - constant, rarely changing pieces become encoded, and the other pieces (testing-related, generally) get sprited out.

With any luck, this post can help someone else out. It seems like there's a lot of information regarding this technique, but it's been haphazardly organized over time. This isn't quite a "definitive" guide, but it aims to be a kickass outline!

Ryan around the Web