eleqtriq

For webdesigners it's still frustrating to deal with the limitations of the CSS "background-image" property. Sure, things have improved ten times with CSS3. We now are able to add multiple backgrounds, have a decent control of sizing and some clipping features. But where is "background-rotation"? Why isn't there a "background-opacity" or a "background-padding" attribute?

So in the end we often find ourselves creating another version of a bitmap if we want to rotate it or need some more padding, feeling guilty because we know that this will cause more traffic and additional http-requests.

I have good news for you: there actually exists a ridiculously easy method to have all this additional attributes and it is supported in every modern browser. Funny enough I did not notice it out in the wild many times. The trick is: create an SVG and use it as wrapper for your bitmap. All the transformations we need can be applied to the image within the SVG. And if it's supported by the browser we can even use these sexy SVG filters. As SVGs are styled with CSS, we can link to the very same stylesheets we use to style our HTML - a convenient way to change attributes consistently across the document.

Using SVG sprites offers a lot of benefits over conventional sprites. Size is not a problem, as a larger SVG doesn't mean bigger file size. Plenty of room to place elements wherever you want them, no need to break your head about the most efficient way to position your tiles. And it is possible to use .jpeg, .png, .webm or .gif together in one sprite. It's even possible to create alpha channels for JPEGs by putting them into a clipping path.

Some Demos

Demos open in new window. Did I mention that KISS is the greatest Rockband EVA?

Caveats

Linking to an SVG which in return links to a bitmap means more http requests and as we all have learned, this is a BAD thing. Therefore we should encode bitmaps to base64 and embed them directly into the SVG. The SVG itself can be base64 encoded as well and embedded into the CSS.

Webkit is the only browser I know of that supports embedding utf8 into stylesheets, so there's no need for base64 encoding, we can paste the SVG code directly into the stylesheet (linebreaks aren't allowed of course ):

"background-image: url('data:image/svg+xml; charset=utf-8,<svg></svg>"

Using a CSS sprite helps to reduce requests even more. SVG gives us the option to define elements in one place...

<defs> <image id="image" xlink:href="myimgage.png"/> </defs>

...and reuse them across the document:

<use xlink:href="#image" transform="fancytransformhere"/>

But this is the web and as you may already have guessed, things are never as rosy as it seems at first glance. There is a certain browser all of you know too well that still needs hacks and tricks to behave like all the other kids. No, I'm not talking of IE, this time Webkit is the black sheep. When embedded as background image, Webkit refuses to load xlink references in the SVG, so images won't show up and external CSS have no effect. This means it is unavoidable to use only inline stylesheets and base64 encoded images if you need proper Webkit support. But even when base64 encoded, there is a bug in webkit that prevents images from showing up in background SVGs. This is because Webkit needs to "see" the SVG at document level at least one time. If this isn't the case, only the vector elements within an SVGs will appear in a background but inline bitmaps will stay invisible.

There is a hack for that, but it's ugly and advocates of proper semantics will hate it: embed the SVG into the html first, i. e. as an 1x1px sized, invisible SVG object. If you don't like this method, javascript comes to the rescue:

(function (svgpath){ if( /webkit/gi.test(navigator.userAgent.toLowerCase()) ){ var obj = document.createElement("object"); obj.setAttribute("type", "image/svg+xml"); obj.setAttribute("data", svgpath); obj.setAttribute("width", "1"); obj.setAttribute("height", "1"); obj.setAttribute("style", "width: 0px; height: 0px; position: absolute;visibility : hidden"); document.getElementsByTagName("html")[0].appendChild(obj); } })("../img/mySVG.svg");

You can remove the SVG object later when css have been loaded.

Recently I needed SVG sprite support in Webkit and created this little hack. It parses a stylesheet for references to SVG background images, then parses every found SVG for stylesheets and images. It then replaces references to stylesheets with inline declarations and images with base64 encoded versions. Finally it will embed the resulting SVGs into the document and remove them after 1 second. At the same time references in the css are replaced with utf8 encoded SVGs.

If you pass "true" as a parameter for "viewSourceMode" (default is "false") it will display the source of all the fixed SVGs in teaxtareas for convenient copying.

Be careful when using it though, it's in alpha state at the moment and speed is an issue when using large and/or many image files, but it can be very helpful during development and for testing.

Trackback

3 Responses to “Better CSS Sprites with SVG”

  1. Comment by Mark 01/09/2012

    Amazing! If I knew this before I wouldn’t have been littering my mobile webkit stylesheets with base64.

  2. Comment by netsi1964 03/19/2012

    Hi Mark,
    Brilliant! yet another an inspirational “eyeopener” from you here at eleqtriq.com. Might we get (yet another) webapp for this purpose? :-)

    /Sten

  3. Pingback by Revision 65: Infinite Transition Delays, jQuery Hooks, ECMAScript 6 | Working Draft 04/03/2012

    […] unangenehm. Noch! Außerdem sprachen wir über das Kapseln von Bitmapgrafiken in SVG, wie von Dirk Weber beschrieben und über die Möglichekeit, eine background-position von unten rechts aus anzugeben (siehe Example […]