Perhaps you’ve heard of data URIs. It’s a really nice way of including a resource that would have otherwise been a separate HTTP request. The format that you use in a data URI can vary. Essentially you just tell it what content type it is (e.g. image/png
), semicolon, then the data of that file.
Like:
<img src='data: ... '>
or:
.bg {
background: url('data: ... ');
}
For a raster image like a PNG, the data of that image needs to be in base64 format. I’m not a huge expert here, but as far as I understand, base64 is safe for use in something like HTML or CSS because it only uses 64 characters known to be safe in those formats.
Probably better Stack Overflow answer by Dave Markle:
You never know – some protocols may interpret your binary data as control characters (like a modem), or your binary data could be screwed up because the underlying protocol might think that you’ve entered a special character combination (like how FTP translates line endings).
So to get around this, people encode the binary data into characters. Base64 is one of these types of encodings.
Base64 looks like gibberish, and we often associate gibberish with compression on the web. But this gibberish isn’t compression, it’s actually a bit bigger than the original because, to quote Jon Skeet on the same Stack Overflow thread:
It takes 4 characters per 3 bytes of data, plus potentially a bit of padding at the end.
I’m not sure how gzip factors into it though. But what I’m getting at here is how SVG factors into this.
You can use data URIs for SVG too.
<img src='data:image/svg+xml; ... '>
.bg {
background: url('data:image/svg+xml; ... ');
}
For SVG, you don’t have to convert the data into base64. Again, not an expert here, but I think the SVG syntax just doesn’t have any crazy characters in it. It’s just XML like HTML is, so it’s safe to use in HTML.
You can leave the encoding in UTF-8, and drop the <svg>
syntax right in there! Like this:
<img src='data:image/svg+xml;utf8,<svg ... > ... </svg>'>
.bg {
background: url('data:image/svg+xml;utf8,<svg ...> ... </svg>');
}
So because we can do that, and we know that base64 often increases the size, might as well do that right? Yep. As a side benefit, the <svg>
syntax left alone does gzip better, because it’s far more repetitive than base64 is. Say you wanted two versions of an icon, one red, one yellow. You can use the same SVG syntax duplicated just change the fill color. Gzip will eat that for breakfast. Credit to Todd Parker for that tip, and that’s also the approach of Grunticon, which data URI’s SVG in UTF-8 into CSS.
A Test
To test this out, I downloaded three SVG icons from IcoMoon.
cog.svg – 1,026 bytes
play.svg – 399 bytes
replay.svg – 495 bytes
I ran them through SVGO just so they are nicely optimized and kinda ready for use as a data URI (whitespace removed, although I guess not strictly necessary).
cog.svg – 685 bytes
play.svg – 118 bytes
replay.svg – 212 bytes
Then I ran those through a base64 converter.
cog.svg – 916 bytes – 133% of the original size
play.svg – 160 bytes – 136% of the original size
replay.svg – 283 bytes – 134% of the original size
So that makes sense, right? If it’s 4 characters for every 3 bytes, that’s 133% bigger, with the variation coming from uneven lengths and thus padding.
Anyway, maybe this is all super obvious. But it just seems to me if you’re going to use a data URI for SVG there is no reason to ever base64 it.
Props to Matt Flaschen for his email a few months ago that highlighted this issue for me.
UPDATE: on IE
There is a lot of talk in the comments about IE 10/11/Edge not supporting this. It does, it’s just finicky. Here’s a reduced test case that works in those version of IE. Note the second example, which is URL encoded, works. The trick is not specifying an encoding type at all.
UPDATE: “Optimized URL-encoded”
Taylor Hunt investigated a bit deeper and found that you can sweak out a bit more optimization by not encoding stuff like spaces and single quotes. Example:
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
Doesn’t work in all browsers. IIRC, Safari 7 and IE11 cannot.
I think that URL encoding the svg solves that, and still performs better in size than base64.
URL encoding? If you mean literal XML within a data URI, you’re suggested to solve a problem with the problem itself. Such does not work in the browsers I mentioned.
I mean
If you meant character encoding as Gunnar Bittersmann suggested below, that might be the solution. I never thought to try that when I was working with data URIs containing SVG.
Yeah, I meat that and I’ve had this issue with IE and FF (back in that time), both solved with this little trick.. You may notice that the raw size of a uriencoded string may be larger than base64 but when gzipped it is definitely smaller.
Yes, the need to URL encode for IE really negates the simplicity. However, I hadn’t thought about the fact that gzip can still make good work of multiple similar SVGs if they are URL encoded but not necessarily if they are base64 encoded.
If you’re not yet at production stage, or are working on an app or something where support for IE is not required, I’ve put together a list of tips on how to avoid breaking your data URIs in other browsers.
To summarize what others have said and tests that I’ve just completed:
Firefox 4+ supports < and >, but # must be url encoded
<=IE11 needs to be fully encoded
Chrome and Safari 5+ work just fine without encoding
You mean 33% bigger, not 133%.
Base64 is commonly used in this scenario because it works around the need to URL escape the image, which would be needed otherwise.
You really need to get some maths lessons, Chris… ;)
916/685 = 1.33 = 133% = 33% bigger, not 133%.
But yeah, base64 is probably not always a good idea but svg can contain unsafe characters (remember, vector image can contain bitmap parts…). Plus, tools like LESS include the SVG as base64 by default when you want a data-uri…
133% of the original size, not 133% bigger than original.
Any difference in browser support between base64 and plain UTF8? I actually switched to base64 because I heard somewhere that the other way didn’t work in IE9 (can’t find source now). Would be nice to see this mentioned in the article either way.
Keep up the good work Chris — thanks!
According to RFC 2397 the parameter in the media type should be an attribut–value pair, i.e.
charset=utf-8
(with hyphen!) instead of justutf8
. With character encoding, the data URI should start withdata:image/svg+xml;charset=utf-8,<svg
…However, when using UTF-8 it should be safe to omit the encoding declaration:
data:image/svg+xml,<svg
…In my experience, not all non-base-64-encoded SVGs work in all browsers. I would love to use this, but it’s been buggy for me.
I wonder if it was an encoded-vs-non-encoded problem rather than a base64 problem.
Another advantage is that you can use variables from CSS preprocessors to build your SVG. Of course, this will bloat the resultant stylesheet, particularly if the same SVG template is rendered multiple different ways. But it does save on an HTTP request, which can be useful for small images such as the navicon.
Just be careful with quotes! If your SVG has an attribute with a double-quote (“) and you open/close your url() attribute with double-quotes then straight SVG will explode. Same goes for single quotes obviously.
If you’re going to use straight SVG then you’ll probably want to put some preprocess in place to normalize (or escape) the quotes.
I have used base64 ( http://www.phoca.cz/cssflags/ ) because raw content didn’t work in all “commonly used” browsers :-(. So even base64 makes the content larger, it was the only one way for me :-(
No, it isn’t. People get this wrong a lot. HTML is not XML, and HTML predates XML by a few years.
HTML History
Interesting.
The point in the case though is: angle brackets, attributes, whatnot, the fact that inline SVG works great in HTML. They are kindred spirits.
Would be cool if there was a SASS mixin. Does anybody has seen one?
Looks like someone on SO created one: http://stackoverflow.com/questions/15454014/url-or-base64-encode-strings-in-compass-sass. It would be great it into compass.
Less.js has an optional
mime type
argument ondata-uri
:Gunnar’s point above about the media type parameter is pretty important…
Using the shorthand
data:image/svg+xml;utf8,
fails in IE10/11Using
data:image/svg+xml;charset=utf-8,
or omitting the media type seems to workI’m not sure if you were saying that
data:image/svg+xml;charset=utf-8,
works in IE, but it truly does not.… with non-encoded svg.
Anyone else seeing something like:
in Firefox? Even better, anyone know a fix?
Are you URL encoding the SVG before putting it into the data URI? Firefox requires it; take a look at the Codepen I posted just above.
You’re right, also the encoding must be set to
charset=utf-8
. Since I’m assuming we all still want tot support FF, I think this should really be highlighted in the article and probably used as the benchmark instead of plan XML (although I’m sure the encoded XML would still win out).Firefox is actually fine with “, it just doesn’t like
#
. This was the only character that I needed to encode in my tests.Uhh… my above comment got messed up.
“Firefox is actually fine with < and >, it just doesn’t like
#
.”Yes, one can really just write plain SVG, but it needs some symbols to be encoded, like quotes, ‘#‘ and brackets. Interestingly, there is no need in encoding equal sign ‘=’, comma and svg namespace url ‘http://www.w3.org/2000/svg’ (consequencently, slash ‘/’ too). IE requires to encode angle brackets. Spaces can be escaped by backslash: ‘\ ’.
However, there is a trick, which can save plenty bytes — use the quoted uri: url(“data:image/svg+xml,%3Csvg xmlns=’http://www.w3.org/2000/svg’…%3C/svg%3E”). Then, one don’t have to encode or escape spaces, and single quotes are ok to use! (But not vice-versa.) This is quite handy, since svg is full of these symbols.
I was just playing around with this and noticed the way Grumpicon handles it:
background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%...svg%3E');
I don’t know enough about character encoding to know why they are using US-ACII and not UTF-8. I see that they do encode the bracket characters and stuff, though, rather than just dumping straight up XML in there.
I do know that I have mad trust for Filament group things in general.
You have to encode the SVG syntax for it to be actually safe to use in all browsers. At the very least it’s IE that requires that. I know chrome can handle the angle brackets and stuff right in there, but IE can’t. So they do the safest possible thing (encode it), but still don’t base64.
Try changing
utf8
tocharset=utf8
.I managed to solve it.
The result is the following:
The problem was the type of url-encoding I was using… I had picked one online, but you need one that actually uses the
encodeURIComponent
function, like http://infoheap.com/tool/url-encode-decode-online-tool/Cheers!
Interesting. When I changed
utf8
tocharset=utf8
in your original pen it worked fine in IE also. Character encoding is a strange brand of sorcery if you ask me.I had better luck with this tool : http://pressbin.com/tools/urlencode_urldecode/
I’ve come up against a weird issue with unclosed quotes. I keep getting an error, but have found a totally illogical fix. All on stack overflow if anyone has ideas:
http://stackoverflow.com/questions/27161390/sass-datauris-unclosed-quotes/27161553#27161553
not sure if anyone posted this, but @Alexis2004 has created a nice Sass function here to use until it gets baked into the Compass build: https://github.com/Compass/compass/issues/1460
works perfectly, avoids having to base64 with the already available Compass inline-image() [http://compass-style.org/reference/compass/helpers/inline-data/#inline-image]