EDIT: removed the charset declarations, since URIs are always ASCII.

Awhile back, CSS-Tricks posted “Probably Don't Base64 SVG”, which concluded that SVGs in data: URIs are smaller if used as-is instead of base64-encoded.

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, we have: (Assume the ... are replaced with real SVG data.)

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

This works in browsers popular with web developers, but Internet Explorer refuses to render it. 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.

Translating Standardsese into English: there are 2 valid methods to encode a data: URI.

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

  2. data:mime/type,[actual data]: URL-encoded. Works better for text data. (SVG, JSON, HTML, etc.)

So the best way of encoding an SVG in a data: URI is data:image/svg+xml,[actual data]. We don’t need the ;charset=utf-8 parameter (or the invalid ;utf8 parameter in the first example), because URLs are always ASCII, and we’re working with a URI. (Yes, you in the back who knows what an IRI is — not now.)

That’s not all. Remember this?

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 vaue, so I’ll show by example. Let’s use the reply icon from IcoMoon that Chris had 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 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! Now, if we were to chuck this minified SVG into a 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, is the only version that works in IE so far. But it’s even longer than the base64 version:

  PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBkPSJNMjI0IDM4Ny44MTRWNTEyTDMyIDMyMGwxOTItMTkydjEyNi45MTJDNDQ3LjM3NSAyNjAuMTUyIDQzNy43OTQgMTAzLjAxNiAzODAuOTMgMCA1MjEuMjg3IDE1MS43MDcgNDkxLjQ4IDM5NC43ODUgMjI0IDM4Ny44MTR6Ii8+PC9zdmc+

HOWEVER.

It’s all in the quotes

You may have noticed that Chris used single quotes to wrap his data: URIs. This was because his unencoded SVG used " for its attributes, so he avoided a collision by wrapping 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 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), non-ASCII characters, and other URL-unsafe characters, like %.
  3. Wrap the URI with double quotes when using it: <img src="">, url("").

In JavaScript, this is the easiest method I’ve found:

  function encodeOptimizedSVGDataUri(svgString) {
  var uriPayload = svgString.replace(/\n+/g, '') // remove newlines
    .encodeUriComponent() // encode URL-unsafe characters
    .replace('%20', ' ') // put spaces back in
    .replace('%3D', '=') // ditto equals signs
    .replace('%3A', ':') // ditto colons
    .replace('%2F', '/') // ditto slashes
    .replace('%22', "'"); // replace quotes with apostrophes (may break certain SVGs)

  return 'data:image/svg+xml,' + uriPayload;
}

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

And that’s how to get the smallest 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,%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,%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 pen 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.


51,689 23 85