My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Tuesday, June 30, 2015

How to serve SVG by default

With retina screens, TV and consoles, old, not so old, modern, and new devices, it makes no sense anymore to serve an image, unless this is a picture.
Logos, buttons, icons, everything that used to be that little png, today can simply be the very same image but served as SVG.

Forget about feature detection

Seriously, I've tried them all ... ! The read only property complete doesn't mean an SVG is supported. In order to be sure, we should draw such SVG into a canvas but even doing so, we cannot be sure the result is that such SVG will be shown at all.
There is only one way to do it right for various reasons: hold your chair, here it comes!

The inline onerror handler

To be sure that the browser will actually see something as meant and as soon as possible, we can use, trust, and recycle inline the onerror handler granting backward compatibility with pretty much every old browser we know.
<img src="image.svg" onerror="Image.onerror.call(this)">
That's truly it, unless you want to name the handler differently or set it on the global scope, same way we are supposed to set basic CSS inline per each page we serve, we could set basic JS too in the header and without needing a network connection at all
<!DOCTYPE html>
<html lang="en">
  <head>
    <script>Image.onerror=function(){if(/\.svg$/.test(this.src))this.src=this.src.slice(0,-3)+'png'};</script>
Bonus for such method is that we could always use it directly as DOM Level 0 onerror in case we need it, and we might also do eventually more with the event object, using as example Image.onerror.call(this,event) per each inline invoke.

But why an inline error in 2015?

Because around 95% of surfing browsers support SVG these days, and it makes zero sense to serve any other decoration or icon as png these days.
Accordingly, only if you would like to support old browsers too, you might decide to use above technique, if you don't care you can simply stick serving SVGs as Image src too.

The inline onerror handler is also the fastest possible way to react to problems instead of using async, defer scripts, or even DOMContentLoaded: the onerror will beat any other technique granting best compatibility!

The technique is indeed meant as "gracefully degraded" experience, so that old browsers will still show the icon as png and don't bother serving 2X icons there, none of these browsers support retina displays.

SVG Hints

Following a couple of hints regarding SVGs
  1. inline support is not as good as image source support, hence feel free to include SVG via <img> but don't inline them in the wild
  2. SVG as Image sources cannot be styled via CSS (and are slightly faster to be rendered because of this too, it's a bit like ShadowDOM in action)
  3. thousands SVGs are slower than thousands PNGs when it comes to raw rendering performance
  4. gzip/deflate compression to serve SVGs will make them not too much heavier than PNGs but will scale any screen and resolution (better for the cache too)
  5. if you have a ViewBox property but you don't have width and height, the SVG might be rendered deformed in some browser (mostly WebKit based gotcha)
  6. if you use SVG external files as CSS url you won't have any possible fallback
  7. IE8, IE7, and IE6 works well with the suggested hint, but using png8 might improve even more the support (or actually with a fallback as GIF that would be universally recognized as such)
For now, I think that's pretty much it. Use it if needed, ignore it otherwise ;-)

What about CSP?

AFAIK CSP is incapable of granting well known inline handlers, even if you use correct sha, which is, for above code:
  • sha1-KQlMcC5gY2yF2YJLHmj92eDINwM=
  • sha256-fdsC+rLqqoTxcoa4lsSYSr9c4HaAmjb7eToXB2X/um8=
If you find a way to enable above goodness via CSP please share, thanks!

What About Web App Icons?

I've honestly no idea why these days we have to write this pile of crap:
<link rel="shortcut icon" type="image/png" href="/img/favicon-16x16.png" sizes="16x16">
<link rel="shortcut icon" type="image/png" href="/img/favicon-32x32.png" sizes="32x32">
<link rel="shortcut icon" type="image/png" href="/img/favicon-64x64.png" sizes="64x64">
<link rel="shortcut icon" type="image/png" href="/img/favicon-96x96.png" sizes="96x96">
<link rel="shortcut icon" type="image/png" href="/img/favicon-192x192.png" sizes="192x192">
<link rel="shortcut icon" type="image/png" href="/img/favicon-194x194.png" sizes="194x194">
instead of just this:
<link rel="shortcut icon" sizes="any" href="/img/favicon.svg">
Please complain with browsers vendors about such ridiculous nonsense, thank you!

5 comments:

deluce said...

Would it be possible to fail faster at the second image ? So if the first fails then all subsequent images should go straight fething the bitmap.

Andrea Giammarchi said...

no, because the document could be sent in chunks and you might not have access to all other images + you cannot control if the browser that already parsed HTML is fetching or it scheduled them anyway.

This technique just work and is very pragmatic, anything more complex would result in troubles for old browsers, the only case where you actually want a fallback, the most problematic.

I wouldn't bother ;-)

kimsbar said...

Interesting gripe about the favicons. But I'm thinking, maybe there's some reason and not just laziness behind it. SVGs are wonderful, but as long as we are viewing them on screens made up of discrete pixels, they will have to be pixelized for viewing, and at very small pixel counts like 16x16, it is much harder to ensure that an svg will pixelize to a clear, sharp image. Favicons are often the site logo, the visual identity of the site, and something you would rather not have end up as a smudgy aliasing disaster, inconsistent in its level of smudgy failing from browser to browser as I suppose they use different rendering algorithms.

Of course this problem will gradually disappear as the average ppi of screens go up, but right now I think a lot of people on desktop and laptop are still viewing 16x16 pixel favicons that it might make sense to wait a bit.

On the other hand I have to admit, just glancing at my tabs, that many sites already seem to happily disregard favicon image quality :)

Nikke said...

CSP can't grant permission to inline error handlers, but your global `Image.onerror` handler should work with CSP Level 2 using the hash or a nonce. Another (admittedly perhaps silly) technique would be to use a seamless sandboxed iframe to wrap the image, allowing inline scripts in them.

Andrea Giammarchi said...

@kimsbar I am right now on a retina screen and GNOME on ArchLinux, many developers are on a retina MacBook pro, and designer on a retina iMac. Users are on retina phones, and retina tablets ... and the icon I've mentioned with all those variants are those suggested to pin to the hoe screen, where you don't really want/deal with 16x16

So, 16x16 is a ghost from the past that could be used as favicon fallback, but has no reason to exist as Web-App icon and even as favicon should contain, as .ico file, its 32x32 counterpart and that's it.

@Nikke the CSP gotcha is extremely annoying because I could use the nonce for any script inline, including the Image.onerror, but I cannot nonce a well known error handler.

Here I say that CSP fails big time and I won't ever suggest to anyone to use an iframe for an image.

Why on bloody earth I cannot whitelist inline errors via the Image.onerror.call(this) nonce is a mystery I bet nobody has a good answer for it.