
Data URIs make CSS sprites obsolete - mnemonik
http://www.nczonline.net/blog/2010/07/06/data-uris-make-css-sprites-obsolete/
======
jashkenas
Jammit is a Rails plugin and command-line tool that uses this technique, along
with MHTML for IE 6 & 7, and compresses JavaScript as well.

<http://documentcloud.github.com/jammit/>

The big advantage here over spriting is not having to deal with the
complexities of trying to jam repeated background patterns into sprites, and
having to compute pixel offsets for your CSS rules. You can just write your
CSS as you normally would, and have the plugin generate appropriate versions
of your stylesheets before deploying.

If you'd like to see a demonstration of the speed difference it can make,
here's a page that loads 100 small individual images:

[http://jashkenas.s3.amazonaws.com/misc/jammit_example/normal...](http://jashkenas.s3.amazonaws.com/misc/jammit_example/normal.html)

And here's the same page, using a single CSS file with data-URIs for all the
images.

[http://jashkenas.s3.amazonaws.com/misc/jammit_example/jammit...](http://jashkenas.s3.amazonaws.com/misc/jammit_example/jammit.html)

~~~
steveklabnik
Considering CSS sprites were something that dhh mentioned as putting into
Rails 3.1, maybe you should see if you can just get that moved into Rails
core, before the sprites work starts.

~~~
jashkenas
Great suggestion. I presume they'll want to roll their own solution (hopefully
taking advantage of existing gems like ruby-yui-compressor and closure-
compiler). But I'll open a ticket for it.

~~~
steveklabnik
> I presume they'll want to roll their own solution

I have yet to contribute to rails-core, but generally, they don't accept
feature requests, they only accept patches. Even if you end up replacing the
'back end' of it with something else, maybe your code would give the feature a
head start.

------
pilif
mmh... there are two kinds of overhead associated with using data URLs.

One is that encoding arbitrary binary data using base64 will increase the size
of the data by around one third, so you will transmit more data.

Of course, if you do the caching headers right, this applies only to the first
connection.

The other overhead you have to pay for every time: The data has to be base64
decoded before it can be used. Depending on the amount and size of images and
depending on the type of implementation in the browser, this can require a
significant amount of resources.

edit: In addition I wonder whether browsers cache the base64 decoded output
between page loads. If you are really unlucky, the decoding might be done on
every page load. Also, I'm not sure how browsers would handle identical data-
content in multiple CSS rules: Do they keep multiple copies of the same image
in memory? Or do they detect identical data and keep only one image around?

I would do some tests before blindly applying this.

~~~
pmjordan
_The other overhead you have to pay for every time: The data has to be base64
decoded before it can be used._

Compared to decompressing the PNG or JPEG data, decoding base64 is essentially
free. It will therefore also not matter much at what stage it's cached.

~~~
jws
Maybe. The JPEG decompression has been mercilessly optimized for decades.

The oddball base64 URI decode might have been done by the team's crappy coder
that no one trusts, but you can write decent test cases for base64 decoding,
so you can burn his hours for a few weeks without him doing any lasting harm
to the code base.

------
pbz
Ideally, in my opinion, when the browser makes a request for
example.com/something the server should send a continuous stream of data. It
could work something like this:

1) The browser sends the request along with a list of files it already has
cached (maybe a special type of cache that never expires so the browser would
know that it wouldn't need that file ever again).

2) Then web server sends the actual HTML that needs to be rendered, followed
by whatever files the browser may need (css, js, images, etc). The browser
would know where the HTML block starts and where other pieces begin, so from
its point of view it's the same as if it requested those pieces. The
difference is that the server anticipates these requests and sends them along
all in one batch. The very first piece would be a list of files the server is
sending, so the browser would know what to expect. At the end of the stream,
if there are any more files that are needed that haven't been included in the
main stream, the browser would just request them as usual.

Doing something like this would effectively render most page accesses to a
single request, going from 10 or more requests to a single request would have
quite a bit of an impact. For this to work though, both the browser and server
would need to support such a feature...

~~~
jws
Yes. HTTP/1.1 arranged for this with _pipelining_ 11 years ago.

But there were servers that thought they supported pipelining and had
corruption issues, so the clients got scared and wouldn't use it. (Besides,
the modems on the edge of the web were the problem, not the latency.) Then the
proxy people said "Why bother? No one uses it.", and the clients continued to
not implement it, or did but left it off by default with a switch to turn it
on in a disused lavatory, behind the "Beware of the Leopard" sign. Meanwhile,
the server people having run out of useful and useless features to add to
their vast code bases actually got around to making pipelining work correctly.

Welcome to 2010.

• Most popular web servers support pipelining, probably correctly.

• <1% of browsers will try it.

• Proxies (firewall and caching) largely break it.

• If you are using scripts that can change the state of the web server, then
your head might explode when you consider what happens with pipelined
requests.

~~~
TimothyFitz
Pipelining is not enough even if the internet infrastructure was perfect. Why?

1\. Slow requests stall the entire pipeline. 2\. The initial request stalls
the pipeline. 3\. The pipeline is being pulled instead of pushed so your
performance is still roundtrip-lag-bound if you have cascading references. For
example an HTML file references a .css file which references PNG costs 3 round
trips at least.

~~~
jws
True, if latency dominates that much in your situation, which can happen with
no congested pipes in the path. An HTML5 "manifest" with pipelining would take
care of all but the initial and 2nd latency.

------
alanh
For me, CSS3 transitions like background fades (.2-.3sec) killed sprites. They
just don't work together.

Some zooming browsers also mess up sprites on the edges at certain zoom
levels.

------
pmjordan
One downside that hasn't been mentioned so far, as far as I can tell: the
conflict between caching and downloading unneeded image data.

If you use image 1 on pages A & B and not page C, but image 2 on pages A & C
but not B, you face a dilemma.

\- If you stuff it all in one big CSS file, visitors to B (C) will download
image 2 (1), even though it's never shown.

\- If you split the CSS in 2, with A including both and B & C including only
one each, you'll have an extra request in A, and require duplication or a
third file if B & C share style rules

\- If each page serves its own CSS file, you have only 1 request but it (and
the image data) won't be cached across pages.

This restricts this technique to images that are used all over the place or
images that are tiny.

~~~
dedward
You would have exactly the same issue when applying CSS sprites... the only
difference here is including the image data in the CSS file rather than a
separate sprite file - removing the need for another HTTP request.

~~~
pmjordan
True, good point. (leaving aside the tiling subtleties, anyway)

------
Jim_Neath
Jammit, the rails asset packaging plugin has had the option to do this for a
while:

<http://documentcloud.github.com/jammit/>

------
jeff18
I believe stylesheets must be downloaded first before any further rendering
can be done. Therefore you block rendering until all images have finished
downloading, while a traditional external image sprite can be downloaded
asynchronously.

~~~
mcav
This Yahoo post thinks that stylesheets don't block the browser, typically:
[http://developer.yahoo.net/blog/archives/2007/07/high_perfor...](http://developer.yahoo.net/blog/archives/2007/07/high_performanc_4.html)

------
superk
The problem I see, is that if you change a single line in the stylesheet, the
client has to re-download all images - nothing is cached.

~~~
superk
I'll add... it really makes sense for mobile/webkit where you can depend a lot
more on CSS3 and most images are small icons or tiles. I've seen Apple use it
a lot in their mobile web apps.

------
chriseppstein
FYI: the compass stylesheet authoring framework makes it drop-dead easy to
work with data-uris in your stylesheets: [http://compass-
style.org/docs/reference/compass/helpers/inli...](http://compass-
style.org/docs/reference/compass/helpers/inline-data/#inline-image)

------
DanHulton
Holy crap is that ever a slick technique.

And the ie6/7 stuff isn't much to worry about, either - you can just use
conditional comments to include a traditional CSS Sprite-based file if ie6/7
is detected.

Yes, it means more overhead, maintaining two versions of your CSS file, but
the improvements in speed might be worth it.

~~~
CWIZO
I seriously doubt it. You only remove 1 extra request with this technique. The
main advantage is that you don't have to maintain a sprite image (but using
this without some tool would also be cumbersome).

IMHO one saved request isn't work doing the same thing twice. And MHTML
([http://www.phpied.com/mhtml-when-you-need-data-uris-in-
ie7-a...](http://www.phpied.com/mhtml-when-you-need-data-uris-in-ie7-and-
under/)) doesen't come as a saviour either, as you have to duplicate all
images in the CSS, which increases the size of the CSS significantly (if it
doesn't you are probably better off using "old fashioned" techniques.

~~~
pilif
in defense of the technique: Parsing an existing CSS file, extracting the
images, base64-encoding and embedding them is something that's very easily
done programmatically and a script that does this on deployment is very easily
created.

Assembling a huge sprite image and emitting offsets is harder. Still very
doable though, of course.

~~~
necrecious
To automatically assemble a huge sprite and emit the offsets is also easy.

Game engines has done this for almost 20 years.

~~~
pmjordan
One additional downside is that sprite atlases are annoying when you need to
tile the image. Vertically repeating images can't be in the same atlas as
horizontally repeating ones. X+Y repeats each need to be in a separate image
altogether.

I'm also wondering if, to correctly emit the offsets in CSS attributes in all
cases, you'll require some manually inserted placeholders.

------
angelbob
Mobile browsers tend to do less caching, won't cache larger files, and don't
caches files for as long. Doing an end-run around their image caching seems
prone to cause problems.

Has anybody tried this technique on a mobile browser, say Mobile Safari on
iPhone, to see how it stacks up?

------
benbeltran
Is anyone else but me worried about the legibility of the CSS with Data URIs?
I mean, I can see how it can be useful, but I think the code is more readable
with "spritesheet.png" than with:
iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGP
C/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IA
AAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1J
REFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jq
ch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0
vr4MkhoXe0rZigAAAABJRU5ErkJggg==

Now if only there was a way to define a data URI in a similar manner to @font-
face. Maybe @data-object or something like that.

~~~
chc
I certainly wouldn't write it inline in the CSS. I'd use a preprocessor to
keep the style itself readable and have the base64 inserted for the deployment
version.

I personally like Sass, but for this purpose CPP or a knocked-together Ruby
script would be just as good.

------
mmaunder
It would be useful to see a matrix of browser/version support for data URI's
embedded in external stylesheets along with anticipated support in future
versions.

------
kwamenum86
With data URIs, using the same image in two different CSS rules will result in
two instances of the base 64 representation of the image in your CSS file.
There are workarounds...none are pretty though. MHTML does not have this
problem. In fact, in some ways I think MHTML is a more elegeant format for
asset delivery.

------
code_duck
As I remember when a sprite was a hardware assisted moving image on a screen,
I really dislike how this term has been adopted. The fact that it uses a
section of a large image does not make it a 'sprite'.

~~~
jra101
A sprite is just a 2D image. Animation in old 2D games would usually store
several frames of animation in a single image, similar to the current use in
CSS.

~~~
code_duck
So, where's the relation to animation here? I know what a sprite is, thanks.
It sounds like you might want to go read the Wikipedia page, on the other
hand.

~~~
csmeder
_In computer graphics, a sprite (also known by other names; see Synonyms
below) is a two-dimensional image or animation that is integrated into a
larger scene._

<http://en.wikipedia.org/wiki/Sprite_(computer_graphics)>

This dates back to the Commodore 64, that said do we really need to down vote
this guy so much. He was misinformed. This doesn't mean we have to be
vindictive.

~~~
code_duck
I still think it is all the 20 year olds who have never programmed or used a
system with hardware sprites that have no idea what they're saying, but I do
appreciate the generous sentiment. Sure, if one is perceived to be wrong AND
jerky and insistent about it, as I was, people tend to downvote your comment.

Anyhow, it's just terminology. I still hate it.

------
tlrobinson
Cappuccino builds this idea in:
[http://cappuccino.org/discuss/2009/11/11/just-one-file-
with-...](http://cappuccino.org/discuss/2009/11/11/just-one-file-with-
cappuccino-0-8/)

