EDIT: removed the charset declarations, since browsers automatically interpret data: URIs with the page’s character encoding.

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.) 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,[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,[actual data]. We don't need the ;charset=utf-8 parameter (or the invalid ;utf8 parameter in the first example), because browsers will interpret the data: URI with the character set of the requesting document, and XML is specced to default to UTF-8 anyway. Saves us a handful of bytes!

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! 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 wrap 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,%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 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.


44,621 23 82