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

It’s got the right idea, but there are a few complications and further optimizations.

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 won’t tolerate it. 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 ways to write a data: URI.

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

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

So the best way of encoding 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. (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 vague, so I’ll show by example. Let’s use the reply icon that Chris did:

  <?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 optimize it with SVGOMG. The result:

  <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 typical 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 '. This 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 = encodeURIComponent(svgString) // encode URL-unsafe characters
    .replace(/%0A/g, '') // remove newlines
    .replace(/%20/g, ' ') // put spaces back in
    .replace(/%3D/g, '=') // ditto equals signs
    .replace(/%3A/g, ':') // ditto colons
    .replace(/%2F/g, '/') // ditto slashes
    .replace(/%22/g, "'"); // replace quotes with apostrophes (may break certain SVGs)

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

// Possible improvements:
//   * Lowercase the hex-escapes for better gzipping
//   * Replace stuff like `fill="%23000"` with `fill="black"`

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 (298 characters):
data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBkPSJNMjI0IDM4Ny44MTRWNTEyTDMyIDMyMGwxOTItMTkydjEyNi45MTJDNDQ3LjM3NSAyNjAuMTUyIDQzNy43OTQgMTAzLjAxNiAzODAuOTMgMCA1MjEuMjg3IDE1MS43MDcgNDkxLjQ4IDM5NC43ODUgMjI0IDM4Ny44MTR6Ii8+PC9zdmc+
Fully URL-encoded (305 characters):
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 (237 characters):
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 works beautifully all the way down to IE9 and Android 3.Butt.


74,475 29 114