CodePen

Optimizing SVGs in data URIs

A while back, CSS-Tricks posted "Probably Don't Base64 SVG", which concluded that if you're using SVGs in data URIs, they're smaller if used as-is instead of with base64 encoding.

It's got the right idea, but there are a few complications and further optimizations to be had.

Better browser support

In the provided code snippet, we have:

  .bg {
  background: url('data:image/svg+xml;utf8,<svg ...> ... </svg>');
}

This works in browsers popular among web developers, but Internet Explorer refuses to work with it. (Assume the ... are replaced with real SVG data, of course.) This is because technically it's a malformed data URI, and IE is being strict.

RFC 2397, which defines data URIs, says:

The URLs are of the form:

data:[<mediatype>][;base64],<data>

The <mediatype> is an Internet media type specification (with optional parameters.) The appearance of ";base64" means that the data is encoded as base64. Without ";base64", the data (as a sequence of octets) is represented using ASCII encoding for octets inside the range of safe URL characters and using the standard %xx hex encoding of URLs for octets outside that range. If <mediatype> is omitted, it defaults to text/plain;charset=US-ASCII.

Parsing out the meaning from Standardsese, there are only two valid methods of encoding a data URI:

  1. data:mime/type;base64,[actual data]: base64, works better for binary data (PNGs, fonts, SVGZ, etc.)

  2. data:mime/type;charset=[charset],[actual data]: URL-encoded plain text, works better for textual formats (SVG, HTML, etc.)

So the correct way of encoding an SVG as a data URL would be data:image/svg+xml;charset=utf8,[actual data]. I guess most browsers are lenient about the charset= string, but it's required for Internet Explorer, and should be included for greatest interoperability (obscure browsers, email clients, god knows what else).

That's not all. Remember this line?

Without ";base64", the data (as a sequence of octets) is represented using ASCII encoding for octets inside the range of safe URL characters and using the standard %xx hex encoding of URLs for octets outside that range.

That means we either base64 encode, or percent-encode characters that aren't URL-safe. "URL-safe" is a bit confusing though, so I'll show it by example. I'll use the reply icon from IcoMoon that Chris used in his tests:

  <?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" viewBox="0 0 512 512"><g id="icomoon-ignore">
</g>
<path d="M224 387.814v124.186l-192-192 192-192v126.912c223.375 5.24 213.794-151.896 156.931-254.912 140.355 151.707 110.55 394.785-156.931 387.814z"></path>
</svg>

We'll do as he recommends and run it through SVGO (or if you're a visual thinker, the GUI version: SVGOMG). That results in:

  <svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><path d="M224 387.814V512L32 320l192-192v126.912C447.375 260.152 437.794 103.016 380.93 0 521.287 151.707 491.48 394.785 224 387.814z"/></svg>

Quite a bit smaller! If you're planning on using CSS to set the dimensions of the image, you can also remove the width and height attributes to slim down even more:

  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M224 387.814V512L32 320l192-192v126.912C447.375 260.152 437.794 103.016 380.93 0 521.287 151.707 491.48 394.785 224 387.814z"/></svg>

Now, if we were to chuck this minified SVG into your average URL encoder, you'd get something like this:

  %3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20d%3D%22M224%20387.814V512L32%20320l192-192v126.912C447.375%20260.152%20437.794%20103.016%20380.93%200%20521.287%20151.707%20491.48%20394.785%20224%20387.814z%22%2F%3E%3C%2Fsvg%3E

Which so far, is the only version that works in IE. Notably, this is even longer than the base64 representation of the SVG, which is this:

  PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBkPSJNMjI0IDM4Ny44MTRWNTEyTDMyIDMyMGwxOTItMTkydjEyNi45MTJDNDQ3LjM3NSAyNjAuMTUyIDQzNy43OTQgMTAzLjAxNiAzODAuOTMgMCA1MjEuMjg3IDE1MS43MDcgNDkxLjQ4IDM5NC43ODUgMjI0IDM4Ny44MTR6Ii8+PC9zdmc+

HOWEVER.

It's all in the quotes

You may have noticed that Chris was using single quotes (') to delimit his data URIs. This was because his unencoded SVG file was using " around its attribute values, so he avoided a collision by wrapping the data URI with ' instead. This seemingly minor change is the key to truly tiny data URIs.

" and ' are both valid attribute delimiters (that is, attribute="value" and attribute='value' both work), but only ' is allowed unencoded in a URL. By swapping the quotes, and encoding < and >, we get:

  %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M224 387.814V512L32 320l192-192v126.912C447.375 260.152 437.794 103.016 380.93 0 521.287 151.707 491.48 394.785 224 387.814z'/%3E%3C/svg%3E

So whenever you're using an SVG as a data URI:

  1. Swap its attribute values' double quotes with single quotes
  2. Encode <, >, #, any remaining " (like in textual content), and other known-unsafe-in-URLs characters (%, for instance)
  3. Wrap the data URI with double quotes when using it (<img src="">, url(""))

Web friend jakob-e implemented this algorithm in SASS, which should make things much easier in a good workflow. Check it out:

And that's how to get the smallest damn data URIs IE (and the standard) can handle. To sum up:

base64 encoded:

  data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBkPSJNMjI0IDM4Ny44MTRWNTEyTDMyIDMyMGwxOTItMTkydjEyNi45MTJDNDQ3LjM3NSAyNjAuMTUyIDQzNy43OTQgMTAzLjAxNiAzODAuOTMgMCA1MjEuMjg3IDE1MS43MDcgNDkxLjQ4IDM5NC43ODUgMjI0IDM4Ny44MTR6Ii8+PC9zdmc+

Fully URL-encoded:

  data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20d%3D%22M224%20387.814V512L32%20320l192-192v126.912C447.375%20260.152%20437.794%20103.016%20380.93%200%20521.287%20151.707%20491.48%20394.785%20224%20387.814z%22%2F%3E%3C%2Fsvg%3E

Optimized URL-encoded:

  data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M224%20387.814V512L32 320l192-192v126.912C447.375 260.152 437.794 103.016 380.93 0 521.287 151.707 491.48 394.785 224 387.814z'/%3E%3C/svg%3E

I used this codepen to test across browsers on, fittingly enough, CrossBrowserTesting, and it seems to be working beautifully all the way down to IE9 and Android 3.Butt.

Comments

  1. Thanks! Learned a lot from this post and got a demo working.

  2. Of course! http://codepen.io/noahblon/pen/xGbXdV Pretty ugly Sass but its working in IE. I checked out Grunticon's output and the are fully encoding their data uris.

    http://filamentgroup.github.io/grunticon/example/output/preview.html

  3. @noahblon Neat. It's cool how you can use Sass right inside it, since you don't have to take the extra step to base64 encode.

    I would try bringing this up to the Filament Group as a possible improvement, but I'm a little intimidated by them. Those guys go hard.

  4. Thanks for sharing :-)

    You could also let SCSS handle the encoding: http://codepen.io/jakob-e/pen/YXXBrp

  5. @jakob-e Oh, very cool. Do you mind if I add that to the post?

  6. Update – I ran into a "stack level too deep" error when encoding large SVG's (~ 2200 chars+).

    Here is a pen with chunked encoding: http://codepen.io/jakob-e/pen/doMoML – and a test page: http://codepen.io/jakob-e/pen/gprLyg/

Leave a Comment Markdown supported. Double-click names to add to comment.

You must be logged in to comment.