Saturday, October 27, 2007

Bad-Ass CSS

You know what's bad-ass? Icons are not bad-ass. GIF files sure aren't bad-ass. Having to create a stupid images/icons directory every time I start a new project, then re-populate the Subversion repository with a million duplicate copies of the stupid "email" icon ain't fucking bad-ass at all.


I know what you're thinking. "But Matt, you could create a separate module for icons," and I'm guessing write some fancy build script that only pulls the files I actually use…


No, no, no. That's way too much work. And work is the antithesis of bad-ass.


This is bad ass:


"WOW. ICONS."

…you reply sardonically. But those images aren't hosted anywhere. I'm not linking to someone's GIF files.

Those images are MADE FROM PURE CSS!


/* Right Arrow icon */
.icon-rarr {
  padding: 8px;
  width: 16px;
  height: 16px;
  background: url("data:image/gif;base64,R0lGODlhCwALANUAAOtxAOqQAOx6AOqZAOxvAOmVAOx5AOmXAOqLAOuBAOt6AOt9AOmcAOtyAOxyAOtwAOt0AOuIAOqaAOt4AOqYAOmYAOqUAOx0AOmbAOFjAOxwAOt1AOqVAOqNAOmSAOqHAOqFAOp%2BAP%2F%2F%2FwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAAAAAALAAAAAALAAsAAAZQQFGlMsAYMRKGyMJpHijQ4SBAFS0tTE4B4fFkrN1wpEP%2BisjkxGf9MbMXoDjIKgcZQngrfn%2FZbKwTgQICCg0aGhkQF4t%2BfwQEDxoAkxoOIkEAOw%3D%3D") center left no-repeat;
/* Alternate: URL encoding
  background: url("data:image/gif,GIF89a%0B%00%0B%00%D5%00%00%EBq%00%EA%90%00%ECz%00%EA%99%00%ECo%00%E9%95%00%ECy%00%E9%97%00%EA%8B%00%EB%81%00%EBz%00%EB%7D%00%E9%9C%00%EBr%00%ECr%00%EBp%00%EBt%00%EB%88%00%EA%9A%00%EBx%00%EA%98%00%E9%98%00%EA%94%00%ECt%00%E9%9B%00%E1c%00%ECp%00%EBu%00%EA%95%00%EA%8D%00%E9%92%00%EA%87%00%EA%85%00%EA~%00%FF%FF%FF%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%00%00%00%00%00%2C%00%00%00%00%0B%00%0B%00%00%06P%40Q%A52%C0%181%12%86%C8%C2i%1E(%D0%E1%20%40%15--LN%01%E1%F1d%AC%DDp%A4C%FE%8A%C8%E4%C4g%FD1%B3%17%A08%C8*%07%19Bx%2B~%7F%D9l%AC%13%81%02%02%0A%0D%1A%1A%19%10%17%8B~%7F%04%04%0F%1A%00%93%1A%0E%22A%00%3B") left center no-repeat;
*/

}

USAGE: <span class="icon-rarr"></span>
RESULT:


OMGWTF?

That is the bad-assery known as the data:uri scheme.


Remember USENET? Remember all those naughty alt.binaries.whatever things you downloaded? Same concept: textual representation of binary data. The data is composed of two elements: the MIME type (image/gif or application/pdf for example), and the encoded binary data (base64 and URL encoding are both commonly understood by browsers). I have read of an upper limit imposed by some browsers of ~4k of data per object, which is a good common-sense limit—otherwise your CSS will start to get ugly. These file sizes are perfect for small images like icons. PDFs would probably be a poor choice. You can create your own online at the data:URI Kitchen.


I can't see anything!

Yeah, that's the big drawback. Shut yer yap and get a modern browser, for chrissake.


Benefits

  1. Speed. Every image the client doesn't need to download means the two pipes that the HTTP spec gives you for server-client connections can be better spent downloading more important shit. If you're GZIPing your text files like you should be, the overall size of the encoded CSS should be about the same as the original binary (update: smaller, actually!). As a style issue, remember that base64 encoding takes up about half as much space in the CSS.

  2. Speed, part 2. Try this out: create a thousand DOM nodes of images, pointing them to the same GIF file. Now, change half of them to randomly point to a new image. Watch grass grow.

    An easier way to do this would be to create a class in your CSS file that points to a different image. Then, when you wanted to change half the images, you'd just change their class name and they'd automatically be pointing to the same instance of the image. A lot easier, and a lot faster. With this method, the browser doesn't have to go to the cache to fetch the image, so client-side performance also benefits.

  3. Basically, this allows you to almost completely remove the file and network i/o from the equation, then increase memory efficiency by an order of magnitude or so.

  4. It's just cooler. If you cannot see that, you shouldn't be reading this blog.

Just one thing…

Every browser on the planet—Firefox, Opera, Safari, even fucking Konquerer—supports data:uri, save one.

I leave it as an exercise to the reader to figure out which one, but here's a hint: You'll recognize the authors of the browser in question because the demons they're shackled to in hell will have grins on their faces. They're the ones whose mothers will burn in the pits of flaming tar for all eternity just for having committed the crime of conceiving them.


Why bother?

  1. The aforementioned bad-assness.

  2. Some people have more control over their client's browser choice (Intranet applications) and might be able to take advantage of it.

  3. In the middle of writing this I had the crazy idea of putting all of Mark James's "Silk" icon set into a single CSS "package" and needed an excuse to see the idea through.

Stay tuned.

2 comments:

AutoSponge said...

I, for one, am glad you're seeing this through. It is completely bad-ass. I had no idea such coolness was possible (partly because I have to use IE for my job).

In a future filled with web-app mashups, I see great potential for this technique to maintain "look-and-feel" over various platforms. You don't have to move 1000 .pngs to a local directory or write-out the complete URL of a .png hosted elsewhere when you port your CSS from one platform to the next, just upload the CSS!

Sweet.

Ronny Ong said...

One of the subtle additions in IE 8 is support for data:uri. See the white paper for details.