Embedding images in CSS

This is an old post. It may contain broken links and outdated information.

I’ve mentioned before that I’m CSS-stupid, and the practical effect of not knowing how to do damn near anything means that even small modifications to the Bigdinosaur.org main site usually involve a tremendous amount of reading and experimentation. I’ve stuck firmly to the current school of thought in web design, which is that HTML is purely for content, and layout should be done exclusively with CSS, and I’ve managed to produce a very simple but nice-looking site as a result.

However, running the site through a few web performance benchmarking sites led to a universal recommendation: use CSS sprites or CSS image embedding to reduce the number of HTTP connections necessary to load the site. Each HTTP session costs the web server a little bit of overhead (although using Nginx instead of Apache helps a whole lot with keeping the web server processes under control), and one of the ways to ensure your site loads as quickly as possible is to reduce the number of times a client has to ask the web server to send it things.

I’m not going to cover CSS sprites, which is where you take all of the images on your page and make one big image out of them and then specify via which regions of the big image to load in order to display the individual smaller images, but I am going to go into how I embedded my images directly into the site’s CSS file itself, because there’s an annoying lack of complete tutorials on the web about how to do it—plenty of places tell you how to embed the images, but damn few explain how to actually display the things after you’ve embedded them.

However, there are some things to be aware of before we start. First, unlike with CSS sprites, there’s a limitation to the size of images that can be embedded. It’s a soft limit that varies by browser, but in practice you shouldn’t embed images larger than 2 KB or so. Second, embedding the images actually makes them a bit larger because they have to be transformed into Base64, so while this will cut down on the amount of transfer overhead, it will also increase the actual amount of data that has to be shipped per page load. This can be somewhat mitigated by making sure that your web server is using gzip compression for CSS files, which we’ll turn on here in just a bit.

What images are potential candidates for embedding? Ideally, small things like buttons or badges which are used on most or all of your site’s pages. By embedding them in CSS, they get cached by the end user’s browser as part of the CSS file; even better, if you’ve got an extremely busy site which might be getting connection-starved, the embedded images will be downloaded before the HTML is downloaded and will be rendered & displayed in parallel, so your page won’t be stuck in blank limbo, unable to display while waiting for a bunch of tiny images to be served up.

The main site displays four 80×15 web badges at the bottom of every page. As separate images, these generated four separate HTTP connections:

I figured that since these guys get shown on every page, they’d be a perfect fit for being folded, spindled, and mutilated into CSS embeds.

Converting to base64

This is the easiest step. Binary files like images are made up of all kinds of crazy characters that fall outside of a web server’s declared character set, so methods exist to convert a binary file made up of a huge range of characters into a file made up of regular ASCII letters and numbers. The encoding of binary data into text has a long and rich history on the Internet and is an innovative way of shoving files through a medium better-suited to the transfer of text, and it’s what we need to do here in order to take our images (made up of not-text) and transform them into text. The downside, as I mentioned above, is that this conversion makes the encoded files larger, since the same data has to be represented using a smaller character set—with Base64, it’s an increase of about 33%, as four ASCII characters are necessary to represent three binary ones.

There are lots of Base64 conversion utilities out there—you can even roll your own, if you’re feeling industrious. I used this online Base64 tool and simply uploaded my images and let it do the work for me, since I am fundamentally lazy. The Nginx web badge is the smallest, and converting it to Base64 yields the following output:


Stuffing into CSS

After converting my four images, I append them onto the end of my site’s CSS file as CSS classes. I decided to use classes instead of IDs because I’m going to display them with img tags, though I’d welcome any discussion about a better way to do it.


/* Base-64 encoded footer images go below */

.footnginx {
    padding-left: 43px;
    padding-right: 43px;
    padding-bottom: 15px;
    height: 0px;
    width: 0px;
    border: 0px;
    background-repeat: no-repeat;

This sets up a class called .footnginx, for my Nginx footer image. When rendered by a browser, the class will have a CSS background image which consists of a URI containing our Base64-encoded image. Note also the padding lines—we’ll get to exactly why those are necessary in a moment.

Updated on 8 March 2012: I discovered that Chrome and IE8-9 were incorrectly rendering the images, and so I added the height, width, and border attributes to fix their wrongness.

Actually displaying the damn things

And here’s where most tutorials I found fell completely flat. There are lots of different methods of CSS embedding and I found tutorials for all of them, but there were only two or three I found that actually talked about what the hell to do with the images once you embedded them. It felt like I was reading cookbooks, which drive me up the wall because they all assume some amount of basic knowledge that I don’t seem to have (Stir? How long?! Beat? Until what?! Reduce? What the hell does that mean?).

So, here’s what I eventually came up with, after a lot of experimentation:

<a href="http://www.ubuntu.com/business/server/overview" rel="nofollow" title="Runs on
Ubuntu"><img class="footubuntu" src="" alt="" /></a>

<a href="http://wiki.nginx.org" rel="nofollow" title="Served by Nginx"><img class="footnginx"
src="" alt="" /></a>

<a href="http://macrabbit.com/espresso/" rel="nofollow" title="Made with Espresso"><img
class="footespresso" src="" alt="" /></a>

<a href="http://flyingmeat.com/acorn/" rel="nofollow" title="Images by Acorn"><img
class="footacorn" src="" alt="" /></a>

The four images are contained within a div that sets text-align: center; so that they’re centered, and then they are displayed as images inside of HTML anchors. The anchors are there so that I can link back to each button’s web site, and the actual displaying is done with the img tags.

Why am I using img instead of div id? Because a CSS div is a block element, and just like a paragraph it gets rendered on its own line. Having four divs in a row would result in each image being displayed on a new line. There’s a workaround, by styling the divs with display: inline; in their CSS definitions, but that carries with it additional layout issues that I couldn’t figure my way around.

The solution I was happy with was simply displaying the images with the good ol’ image tag and then let the CSS class provide the image’s URI. However, I immediately ran into a big snag in that the images were displayed as…nothing.

The solution I was happy with was simply displaying the images with the good ol’ image tag and then let the CSS class provide the image’s URI.

If I started adding in alt text, I could get the images to start showing up. If I added a bunch of non-breaking spaces to the alt text, I’d get as much image showing as I had spaces. I was baffled by this and started to get annoyed, and padded out each image’s alt text with a bunch of alt text to make them all show up, but this was hack-y and ugly. There had to be a better way.

It dawned on me—and no doubt someone more familiar with CSS would have twigged to this much sooner—that I was using the background-image CSS attribute to display the images, and CSS-declared background images get treated differently from regular img-displayed images in that they’re a background, not the actual thing being displayed. Without something to display—like the spaces I was putting into the alt tag—there was nothing to put the background image behind.

That’s the reason for the padding attributes in each class. The images are 80 pixels wide, so from the center I added 43 pixels to the left and right, which gives enough empty space to display the background image and also add some separation between the images. I also added a little bit of padding on the bottom because at 15 pixels in height, the images are just slightly taller than a line of standad text.

GZIP for great justice (and compression)

The last thing to do is to ensure that the web server is compressing CSS files as it sends them, which will help to make up for some of the bloat added to the images when converted to Base64. Since Bigdinosaur.org runs Nginx, here’s the section of nginx.conf to enable gzip compression for CSS files, along with other filetypes which contain primarily text:

# Gzip Settings
gzip on; # Enables gzip compression
gzip_disable "msie6"; # Prevents sending compressed files to IE 6
gzip_min_length 1100; # Don't waste time compressing files < 1100 bytes
gzip_vary on; # Enables "Vary: Accept-Encoding" support
gzip_proxied any; # Enables gzipping of proxied requests
gzip_buffers 16 8k; # Sets larger-than-default working buffers
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

You can verify your CSS file is being compressed by examining the HTTP headers when you load your page; if things are working correctly, you’ll see a header that looks something like Accept-Encoding: gzip, deflate your CSS file’s GET request, followed by a Content-Encoding: gzip in the server’s 200 response.

And that’s it! Like most of the other entires in this blog, this one is mainly for me—by writing this all down here, I don’t have to refer back to fragmented notes (or, worse, just try to remember off the top of my head) to try to re-figure out how I made this work the next time I want to do it.