If you are considering using data-uris instead of CSS sprites for your background images, you are probably doing this (at least partly) for performance reasons. Each image you can stuff into a data-uri saves you an HTTP call, after all.
But if you are thinking about performance, you will also be paying attention to the size of your CSS, and you might be concerned about the use of the same background image multiple times, but in different contexts. With image files, referencing the same background image multiple times is lightweight, but with data-uris, the base-64 encoded data takes up a lot more space:
.monkey { background-image:url(http://sunpig.com/martin/code/2011/gzipcss/erlenmeyer.png); } .fez { background-image:url(http://sunpig.com/martin/code/2011/gzipcss/erlenmeyer.png); }
vs.
.monkey { background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAlotsmoredata..."); } .fez { background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAlotsmoredata..."); }
There are, of course, other ways of assigning the same background image to different elements (you can group your selectors, or create a mixin classname for use in your HTML). But if, for whatever reason, you do end up embedding the same base-64-encoded resource multiple times in the same file, provided you are serving your CSS gzipped, you don’t need to worry too much about the extra file size.
The reason for this is that the DEFLATE algorithm (which powers gzip compression) is all about eliminating duplicate strings. When the algorithm finds the second instance of a long base-64-encoded string, it can just replace it with a reference to the first instance. And because the reference is much shorter than the base-64 block itself, the size increase of the gzipped file is negligible.
Here’s a short table showing some actual figures. The files 1.css, 2.css, and 3.css contain 1, 2, and 3 copies of the same base-64-encoded image respectively. I used nginx (0.7.67) on my laptop to check the gzipped file sizes.
File | Uncompressed size (bytes) | Gzipped size (bytes) |
---|---|---|
1.css | 1437 | 1139 |
2.css | 2871 | 1135 |
3.css | 4311 | 1156 |
Note that the file with the data embedded twice comes out even smaller than the version with only one copy. gzip can be a bit strange that way, which is why you should always check the gzipped version of any CSS or JS files you’re fine-tuning for size. Minor optimizations don’t always work out the way you might expect.
Just for clarity: embedding the same resource multiple times is not always a great strategy for re-usable CSS. But when you do it, at least you don’t have to worry about file size bloat.
A few comments, seeing that I wrote a performance testing tool for websites not so long ago 🙂
Put the above together, and you’re likely to trade in some small latency on the first request of an image file for potentially substantial bandwidth increases (which in turn affect latency again even in an ideal world, but more so when you’re using a web server that doesn’t deal well with multiple persistent connections); the specifics depend largely on how often your CSS changes relative to your images.
More specifically, the latency improvement you’re gaining is all about how fast the browser parses & applies CSS: with an in-line image, it can render the image the moment the CSS is applied.
The upshot? I’d probably try serving your images from a separate webserver, possibly powered by node.js or a similar lightweight server implementation that is great at handling tons of connections. It’ll help with scalability (quite a bit) while hurting latency only on the very first request of an image in comparison with embedding images in CSS.
None of which suggests that what Martin wrote is wrong. Absolutely not. I just wouldn’t recommend embedding images in CSS in all but the most extreme cases. As always, though, it’s only testing that can tell you wether it’s a good idea or not…
The other big reason for using data-uris instead of external images, which I didn’t touch upon at all, is for maintenance when it comes to image sprites. If you’re creating your sprites by hand, rather than using a tool, you spend a lot of time tweaking background-position offsets, which gets to be a pain in the nuts after a very short while. Using data-uris means that you can treat each image resource separately, just as if we used to do before the spriting technique became popular.
Using multiple data-uris in a single CSS file does not bypass another problem with using sprites, namely that if you change even one image within a large sprite file, a user has to refresh the whole file instead of grabbing it from cache. If you change a single data-uri in the CSS file, the whole cached CSS file is invalidated instead.
If you intersperse data-uris wth your regular CSS, the increase in CSS download and parse time is significant. Browsers will generally block rendering until the CSS is available. By bulking up your CSS, it will take longer for first-time viewers to see their first content.
A possible way to balance this would be to create a separate CSS file containing the data-uris, and place the <link> tag for it at the end of the page (or load it with an asynchronous piece of script). This would allow the primary CSS to be loaded just as quickly as before. You still end up eventually loading a single big file with images, but this way it’s a (gzipped) CSS file with multiple data-uris instead of a PNG file with multiple sub-images; the maintenance benefits of not having to worry about background-position offsets are preserved, at the cost of a small increase in file size.
The most imporant point that Jens makes is that you should test this stuff for yourself. Sites have different visitor profiles, different performance requirements, and different constraints. You can’t improve what you don’t measure. You have to understand your own case, and understand all the techniques available to you, before you can come up with a good solution.
Also of interest here is what Google is doing with their instant preview. See Instant Previews: Under the hood.