Optimizing SVGs in data URIs
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 totext/plain;charset=US-ASCII
.
Translating Standardsese into English, there are 2 valid ways to write a data:
URI.
data:mime/type;base64,[actual data]
: base64-encoded. More efficient for binary data. (PNGs, fonts, SVGZ, etc.)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 the given SVG is ASCII.
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:
- Swap its attribute values’ double quotes with single quotes.
- Encode
<
,>
,#
, any remaining"
(like in textual content), non-ASCII characters, and other URL-unsafe characters, like%
. - Wrap the URI with double quotes when using it:
<img src="">
,url("")
.
UPDATE: I went ahead and made it a dang Node.JS module already: mini-svg-data-uri
. It includes some more optimizations than the original code I posted here. You can also check out the source on GitHub.
Web friend jakob-e also 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:
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.