Ninja SVG sprites

Traditional image sprites for web are done using png or jpg files where multiple small (raster) graphics, often icons, are merged into a single image file to save on file size and reduce amount of http round trips required. This works great unless you want to scale your icons. Wouldn't it be great if you could have spritesheet full of scalable vector graphics? Just imagine how you could enhance your responsive design? Sadly you can't do sprites like that. Or can you?

In this article I describe various methods of creating svg sprites, provide examples and outline browser compatibility.

Embed SVG in CSS

JSFiddle example.

Supported browsers: Chrome and Safari.

Using Data URI scheme you can embed data inline and fed it to the browser. This feature is commonly used for raster background images that are small enough (even after encoding binary to base64) to outweigh disadvantages of this technique. In the example above the svg file is not converted to base64 to save on the final length. This method has very limited browser support rendering it unusable in real-world scenarios (unless maybe you're writing a chrome plugin).

Steps to produce an SVG for embedding in css:

  1. Save your document, remember that the dimensions of the document should fit the contents. If you're using inkscape select option 'optimised svg' and/or follow step 2.
  2. You can greatly reduce the size of your svg file using Scour - a python script designed for this purpose. It's important to remove size information and enable viewboxing, otherwise browsers will treat your image as a raster graphic when resizing (mdn reference)
  3. Remove new line characters from the newly-created svg file
  4. Embed contents of the svg file inline in your css file, remember about the content type or use JSFiddle above as an example.

Steps 2 & 3 can be easilly scripted:

python2 scour.py --enable-comment-stripping --enable-id-stripping --indent=none --remove-metadata --shorten-ids --enable-viewboxing -i input.svg -o output.svg | tr -d '\n'

Embed base64-encoded SVG in CSS

JSFiddle example.

Supported browsers: Chrome, Safari, Firefox, Opera, IE9+

Very similar to the method above, this time we base-64 encode the svg file before embedding in css. This increases the final length by approximately 1/3 (much less when contents is delivered gziped) but also greatly sorts out browser compatibility issue. This technique is more robust but still has all the drawbacks of embedding data using data uri scheme (mainly messing with browser cache).

On Linux/Unix (and probably Mac) you can base64-encode file using a simple command tool called uuencode (also removes irrelevant header, footer and all new line characters):

uuencode -m image_min.svg a | tail -n +2 |head -n -1 | tr -d '\n'

The output can be used for the url argument in your css, again remember you need to include content type and information about base64 encoding as it's done in the JSFiddle above.

Using :target pseudo selector inside your SVG

JSFiddle example.

Supported browsers: Firefox, Opera (broken), Internet Explorer (almost)

This technique makes a clever use of the fact that you can use CSS to show/hide items inside your SVG. Each shape (or group of shapes) is given an ID and a css class. The class is used to hide all shapes by default. The ID is used to uniquely identify a single shape. When a fragment identifier is present in the url (i.e. everything after hash, e.g. http://doppnet.com/article#section1), element with matching id can be targeted in the css by using the :target pseudo selector. We will use this to show a selected shape while leaving all other hidden.

I believe this clever trick has been first published by Erik Dahlström. It's a very promising technique but it might take a while before it's ready for mainstream usage. Currently Webkit-based browsers will only accept fragment identifier in the SVG when it's embedded using 'iframe' or 'object' elements but not 'img' nor inside the 'url' argument in CSS. This problem has been reported on Webkit bugzilla. Opera doesn't always render correct sprite. Internet Explorer 9 will render everything correctly, but will go back to the server once per each sprite which makes the concept counter-productive. Firefox is the only browser that currently fully supports this.

Steps to produce an SVG sprite that can be used in conjunction with fragment identifiers:

  1. Identify shapes (or more likely groups of shapes) within your svg document that will become your sprites, give each an id. Make sure your sprites have relatively identical dimensions.
  2. Move all sprites to position 0,0
  3. Resize the document size to fit the contents.
  4. Optimise the SVG as usual but remember that ids must remain untouched
  5. Paste the following css somewhere in the document (just before </svg> tag will do):

<defs><style>
svg .sprite { display: none }
svg .sprite:target { display: inline }
</style></defs>

You can now use fragment identifier part of the url to select which sprite should be displayed.

I've written a little helper script to automate process above, you can use the script like this:

./svgsprite.py ninjas.svg --id-prefix="ninja" > output.svg

Alternatively you can try SVG-Stacker which apparently has been created for similar purpose and accepts multiple input files.

Summary

At the time of writing , there is no 'silver bullet' method for achieving SVG Sprites. A combination of above methods combined with browser-targeting could potentially be used to achieve efficient, scalable SVG sprites. I suspect that in near future Webkit engine will be patched to allow work with fragment identifiers in SVG correctly which could potentially trigger a wide adoption of this technique for the web.