There is a very clever technique by Alexey Ten on providing an image fallback for SVG going around the internet recently. It does just what you want in the classic no-SVG-support browsers IE 8- and Android 2.3. If we dig a little deeper we find a some pretty interesting stuff including a bit of unexpected behavior that is a bit of a bummer.
Alexey’s technique looks like this:
<svg width="96" height="96">
<image xlink:href="svg.svg" src="svg.png" width="96" height="96" />
</svg>
The idea was based on Jake Archibald’s revisiting of the fact that browsers render the <image>
tag like <img>
. It’s just a weird alias from the days of yore that can be exploited.
What Displays
The technique is pretty close to perfect in terms of what supporting and non-supporting browsers display. IE 8, which doesn’t support SVG, it displays the fallback PNG. Android 2.3, same story.
The part I got confused on was iOS 3 and 4. The native browser on those operating systems do support SVG, in the form of <img src="*.svg">
or as a CSS background-image
. But with this technique, the PNG displays rather than the SVG. The trouble is that iOS 3 & 4 don’t support “inline” SVG (e.g. using SVG with an <svg>
tag right in the HTML) (support chart). SVG support isn’t as simple as yes/no. It depends on how it is used.
The point: it’s weird to see <img>
work and <image>
not work in these cases. And since those browsers do support SVG, it’s a shame not to use it.
What Downloads
Of course we also care about what gets downloaded because that affects page performance. Again it’s mostly good news. Modern browsers like Chrome only download the SVG. Android 2.3 only downloads the PNG.
But we run into an issue with Internet Explorer versions 9, 10, and 11 (a preview at this time). In IE 9, you can see both images turn up in the Network timeline.
The PNG ends up with a result of “Aborted” and listed as 0 B received, but still affects the download timing.
It is a similar story in IE 10, it just seems to abort quicker and move on to the SVG without much downtime.
Scott Jehl suggested using Charles Proxy to test what is being downloaded more accurately. Charles verifies that the PNG is being requested.
The size of the response body is indeed 0 B, so the abort happens fast enough that not much data is transferred. Although the request and response header combined is 782 B and it takes ~300 ms to happen. A Yoav Weiss points out, the problem may be worse if there are blocking scripts at the top of the page.
Also note there is some chance that using a proxy affects what is/isn’t downloaded as Steve Souders points out in this Jason Grigsby article about Charles Proxy.
Research by Andy Davies suggests that the PNG request isn’t aborted at all in IE 11 on Windows 7.
@stopsatgreen @jaffathecake @chriscoyier In my IE11 / Win7 response isn't aborted and shows up in dev tools and pcap pic.twitter.com/ypitwTYAAH
— Andy Davies (@andydavies) August 19, 2013
Findings
I did the display tests from this Pen, but the download tests I made sure to do with just one of the techniques present on the page and using Debug View so there was nothing else on the page but the raw technique.
See the Pen SVG Tests by Chris Coyier (@chriscoyier) on CodePen
So, Fallbacks
The Alexey Ten is still clever and if the iOS display issue and IE download issue is acceptable to you, it’s still usable. Here’s some more fallback techniques, which differ depending on how you use SVG.
If you’re using SVG as a background-image…
Modernizr has an SVG test. So you could declare a fallback with the class names it injects onto the HTML element.
.my-element {
background-image: url(image.svg);
}
.no-svg .my-element {
background-image: url(image.png);
}
That shouldn’t have any double-download issues but doesn’t have the Modernizr dependency.
A very clever technique with no dependency at all comes is to use a little CSS trick with multiple backgrounds and old-syntax linear-gradients:
.my-element {
background-image: url(fallback.png);
background-image:
linear-gradient(transparent, transparent),
url(image.svg);
}
There was a technique going around that just used multiple backgrounds here. But that didn’t quite get the job done because Android 2.3 supported that but not SVG and thus broke. This combines old-syntax gradients with multiple backgrounds, so it works everywhere. Reference.
If both those things are supported, the browser will use the second declaration (with SVG), otherwise fall back to the first declaration (with PNG).
If you’re using SVG as inline <svg>…
David Ensinger posted a technique using the <foreignObject>
tag in <svg>
. But the trouble with that is the fallback is loaded no matter what resulting in the double download all the time instead of just sometimes like the <image>
technique.
This gets a bit complicated, but Artur A posted this idea which seems to solve the double download and work everywhere:
<!DOCTYPE html>
<html>
<head>
<title>HTML5 SVG demo</title>
<style>
.nicolas_cage {
background: url('nicolas_cage.jpg');
width: 20px;
height: 15px;
}
.fallback {
}
</style>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0">
<style>
<![CDATA[
.fallback { background: none; background-image: none; display: none; }
>
</style>
</svg>
<!-- inline svg -->
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40">
<switch>
<circle cx="20" cy="20" r="18" stroke="grey" stroke-width="2" fill="#99FF66" />
<foreignObject>
<div class="nicolas_cage fallback"></div>
</foreignObject>
</switch>
</svg>
<!-- external svg -->
<object type="image/svg+xml" data="circle_orange.svg">
<div class="nicolas_cage fallback"></div>
</object>
</body>
</html>
Alternatively, you could do something like:
<svg> ... inline SVG stuff ... </svg>
<div class="my-svg-alternate"></div>
Then use the Modernizr SVG test again to get the support HTML class and then…
.my-svg-alternate {
display: none;
}
.no-svg .my-svg-alternate {
display: block;
width: 100px;
height: 100px;
background-image: url(image.png);
}
Or wrap the SVG in that div so the div could be used for consistent sizing.
If you’re using inline SVG, there is a good chance you are doing it with <use>
, in which case the script svg3everybody does a pretty good job. If SVG the way you are using inline SVG is supported, it just works. If it’s being referenced externally in IE (which doesn’t work), it makes it work by ajaxing it in. If it doesn’t work at all, it has a syntax you can use to specify a PNG fallback.
If you’re using SVG as <object>…
You could use the object tag itself as the element to style after the Modernizr test.
<object type="image/svg+xml" data="image.svg" class="logo"></object>
.no-svg .logo {
display: block;
width: 100px;
height: 100px;
background-image: url(image.png);
}
If you’re using SVG as <img>…
There is a technique which a failing SVG file can get swapped out on the fly:
<img src="image.svg" onerror="this.src='image.png'">
That requires special HTML as you can see, so if that’s not possible or practical for you, you could swap sources with Modernizr. This uses the JS API of Modernizr, not the class names:
if (!Modernizr.svg) {
$("img[src$='.svg']")
.attr("src", fallback);
}
Where fallback
is a string of a URL where the non-SVG image fallback is. You could keep it in a data-fallback
attribute, use a consistent URL pattern where it just replaces .svg with .png, or whatever other smart thing you can think of. SVGeezy is a library that does exactly this, and uses a clever detection method.
The trouble with those methods is that the require an <img src>
and that src is going to be prefetched and there is no way you can fight it. So you’re looking at potential double-downloads which is always bad.
Or you could use a similar technique as with the inline SVG and object techniques where a hidden DIV is displayed with a fallback.
Another pretty good option is using Picturefill. The <picture>
element allows for content-type fallbacks. You’ll need the polyfill because picture isn’t very well supported yet. This solves the double download problem though, which is great, but doesn’t work without JavaScript. But then again neither do the other methods so. It looks like this:
<picture>
<source srcset="graph.svg" type="image/svg+xml">
<img srcset="graph-small.png, graph-medium.png 400, graph-large.png 800" alt="A lovely graph.”>
</picture>
Remember one kind of fallback is nothing at all
If you’re using the SVG as a background-image, that’s probably decorational and non-vital to the site working, so that’s likely a situation where no fallback is OK.
Similarly with SVG embellishments of any kind, like an icon that is accompanied by a word in a button. Without that SVG, it’s still a button with a word, so acceptable fallback.
Alrighty Then
If this post didn’t mean much to you because you aren’t using SVG yet, you should because it’s awesome. This post could help you get started.
If you have more data to share, we’d love to hear.
Good luck!
affects.
Might want to chuck
this.onerror = null;
into theonerror
method, so you don’t end up with infinite attempted replacements goin’ on if the fallbackpng
request should error out:<img src="image.svg" onerror="this.src=image.png;this.onerror=null;">
+1 to Mat’s point. I couldn’t fit that in the tweet :)
There is another problem with IE and image used as css selector. IE alias the selector ‘image’ to ‘img’.
Never mind that the styles are in the head or separate CSS file:
http://codepen.io/Kseso/pen/vdzJy/
I like to use Modernizr and JS to swap SVG files in image elements. Similar to what’s done above. However, if I have a lot of SVG files on a site, I use this script to fallback to PNG files.
Nice technique, Steve. I use a similar approach:
Nice work on the breakdown, though I think the benefits GREATLY outweigh the issues at hand. iOS 5+6 are basically 96%+ of the iOS devices out there (according to this June report) so I’m not sure how many people should care about iOS 3/4.
As for the IE’s – which is probably the biggest reason people use the fallback – I’ve personally always gone with the “if you really want to use IE, you face the consequences” approach. You present modernizr as the biggest, “safer” alternative but is it even that much better?
Quickly creating a custom build with the default extras, and checking just the 2 SVG options, the library already spits a 8kb minified file. If I use modernizr, I’m basically adding those 8kb to ALL my browsers JUST so IE’s don’t download twice? Unless your site is primarily IE users, that’s just silly, and I rather just let the 5% or whatever of IE users download the extra image, which in most cases (I’m thinking a logo or something) wouldn’t be much bigger anyway.
Depends on how many SVG files you use on a site. Plus Modernizr has a host of other uses. I use it for one reason or another on just about every site I make these days.
Plus modernizr would be included in your global js probably, and is a single/cached cost where each SVG is its own double-download cost. Fair points though. Like all things ever you need to weigh out what works for you.
Inlining SVG in HTML is not the question of supporting SVG format, it’s the question of supporting HTML5 parsing rules. Prior to HTML5 parser, HTML browsers were not able to recognize elements from SVG namespace in the markup served as text/html. Firefox 3.6- and other browsers that haven’t implement HTML5 parser behaved the similar way as iOS5- browser. However, they all supported inline SVG for application/xhtml+xml.
But I suppose that currently iOS5- doesn’t make much problems since its share is less than 8% (http://www.14oranges.com/2013/08/) and, according to my experience, for such old non-Retina devices PNG seems to be a better option.
This is a nice write up exploring this technique. I’ve been following along with the twitter conversation here and there, but its nice to see all this documented.
To prevent the download of the png in IE9+, could you wrap a conditional comment that limits it to IE8 and lower around the image element in the svg. The issues I see with this is that non IE browsers that don’t support SVG wouldn’t see the png fallback so you would need an OR operator in the conditional comment to account for browsers like Android 2.3’s and lower native browser.
I’m thinking something like this:
I don’t have a device to test this in old IE or old Android to see if it works, but I believe this should prevent the double download issues you mentioned.
I’ve got an example here that shows this idea in action. I got pretty close to a decent solution, but alas, it still has issues.
I was able to prevent the double download issue in IE9+.
I’ve tested in IE 10, IE 9, IE 8, and IE7.
I’ve found that using the conditional comment does prevent the double download in IE9+ and shows the .png just fine in IE8 and IE 7.
In IE8/IE7, when I used the code snippet I posted above earlier, it rendered an empty image icon next to the png, for the image without the src attribute for the svg. So I added some inline styles to the svg and that seemed to do the trick to hide the image for the svg rendering as a empty image icon in IE8/IE7.
Here is the code
The drawbacks
This broke the .png showing up in old Android. I only tested in 2.3, but would bet its the same with older versions. Instead, a empty box renders with a gray border around it.
I’m gonna play around with this some more to see if there is anyway around this Android issue while still preventing double downloads in IE9+
Though what I think would be hugely beneficial is, if we had a native HTML element that allowed us to provide fallback images without double downloads if the browsers didn’t support the main image type we decided to use, be it .svg, .webp, a new responsive image format or whatever.
Chris, I’m afraid using Modernizr for SVG in
<img>
andbackground-image
isn’t simple as you’ve suggested. It all goes back to your original confusion over browsers having different support for SVG used in different contexts (<img>
, background, inline, etc).Modernizr.svg
only detects support for SVG in an<embed>
or<object>
Modernizr.svgasimg
is what you need for SVG in<img>
– although it won’t be included until v3.0 (code is here if you want to use it now) and it’s tricky because it’s async – more details belowWe don’t know of a detect for SVG in background images, but as that thread says, you can use
Modernizr.svgasimg
if you’re carefulModernizr.inlinesvg
is what you need for inline SVG (although you managed to avoid needing Modernizr in your example – good news)Async
I said
Modernizr.svgasimg
is async… that means the page will start rendering before the test completes, so your example will request both images:.my-element {}
will match, then shortly afterwards.no-svgasimg .my-element {}
will match.My WebP tutorial explains patterns to avoid this (
Modernizr.webp
is an async detect too, and has very similar use cases). The<img>
case is awkward and is best handled withModernizr.on()
– read the article for that one – but here’s a covers-all-eventualities pattern for background images (if you cheat and useModernizr.svgasimg
):So it turns out the internet it hard. Who woulda thunk it?
Some nice thoughts going in here for sure. Love the fact. Although, I hope there would be better options available in the future
Regarding the issues with IE and its preload scanner recognizing the
<image src>
, perhaps someone could test some of the crazy parser hacks to see if they fool the scanner? For example,<image/src=foo xlink:href=foo>
parses correctly in inline HTML.I don’t have the necessary OSes to test this right now, though.
Yeah, me neither…
But I reckon it’d be interesting to see someone else who has them chime in on this…
~ Steve
Tested this in IE9 and IE10. Here’s the code I used. http://codepen.io/bjankord/pen/xlgfC
In IE10, the image for the png aborted, same as Chris’ tests. In IE9, the png did not abort, and was downloaded. It would seem this is performs less optimally than Chris’ demo :(
Wonderful work, all of you.
Is this a typo? I’m guessing it should be JS, not HTML required for this method?
I mean it requires the
onerror
bit right in the HTML on every single image. If you’re using a CMS that injects the images, perhaps the code that does that can be altered. But for example on CSS-Tricks, there are thousands of posts containing images where that doesn’t exist (they aren’t SVG, but you know what I mean), so it’s not practical to go back and update them to have it, and another method would need to be used (easy enough).As noted, the
switch
andforeignObject
technique I use downloads both SVG and fallback, but I don’t think it’s too terrible a performance hit so long as both SVG and fallback are optimized (and ideally base64 encoded, in the case of the fallback). I’ve been surprised that there’s not more written about the technique, given that the negatives aren’t so bad for smaller images.For the logo on my website, the whole shebang is 5.55kb and of that, the base64 encoded PNG is 2.33kb (which I first ran through ImageAlpha and then ImageOptim to cut down on filesize). I’m saving two HTTP requests, getting pretty solid browser coverage, and it’s only at the expense of mobile browser performance with decoding the base64 (although the jury seems to still be out on that one) and a lack of (or shorter length of) cache for the image. If the images are small, it makes sense to me to lump ’em both together, in spite of the negatives.
That said, I’m definitely intrigued by this
image
alias technique. Thank you Chris for consistently taking a pragmatic approach to all of this as well. There are so many factors to consider and you’ve done a nice job explaining it all.Really nice roundup.
For using
<img>
tags for SVG, I have a slightly extended version of theonerror
method, using conditional comments so oldIE gets the fallback PNG without any JavaScript intervention or extra HTTP requests:http://davidgoss.co.uk/2013/01/30/use-svg-for-your-logo-and-still-support-old-ie-and-android/
We did some device testing yesterday and noted the results here: http://waterpigs.co.uk/notes/4RdDTh/
tl;dr: Most of our devices requested just the SVG apart from IE 10 which requested both, old iOS mobile safari and IE8 which requested the PNG. The most surprising result was the Kindle requesting the PNG when it supports SVG.
Also, the fact that SVG <image>s can’t be sized using percentages is weird and annoying. Anyone know of a fix?
Relative to the sizing, this stack exchange thread proposes a kind of a solution relative to percentages:
http://stackoverflow.com/questions/16813829/responsive-inline-svg-content-of-svg-must-fill-parent-width
I haven’t tried it myself, but I’m looking into combining the suggestions there with suggestions provided here. I think we’ll see the SVG landscape morphing significantly in the next year(s) in response to device evolution.
Here’s a snip from the solution I’m referring to:
Continuing further into the DOM, the 100% height declaration on your svg element is forcing the svg to expand to the overly tall wrapper. And that’s another part of the culprit.
The solution I use involves intrinsic ratios. CSS like this:
In case the thread disappears, he references a fiddle:
http://jsfiddle.net/pcEjd/
Charles shows zero bytes transferred because it’s a cache it (see the 304 status). In my tests the whole PNG gets received before it gets a chance to abort.
Bah, I mean “a cache hit”.
For SVG as a background image, i like more the trick used in your other SVG article
Despite there being various responsive image solutions and fall back methods for them and svg as the post discussed, we still need an actual solution to be introduced in the html5 spec to give us higher compatibility and accessibility going forward.
I just set a PNG background using a single background definition and an SVG background using a multiple background definition.
Old IE falls back to the PNG image, while modern browsers use the SVG.
See the following code example.
HTML :
CSS :
This solution is really nice but I found out that putting links around it resulted in some unwanted space below due to the nested
<image>
tag. If anyone stumbles into the same problem I came up with a solution that hopefully works out in most cases:HTML:
CSS:
I found that using scott’s “onerror” technique worked well but using “IE8 mode” in IE 10 didn’t work. It does work in actual IE8 though so I can live with it. Hope that saves someone the time it took me to work it out!!
Thank youuuuuu.
One thing I found was that it can be time consuming to create all of the PNG fallback images from the SVGs, so I’ve written a little app to speed up that process. Anyone interested can try the beta here: http://svgtoimage.com/ – I’d love to get some feedback.
What not have the object tag use its own fallback mechanism and skip the Modernizr+CSS step?
This is the only one working for me, though I’m using ie9 and switching the browser mode to IE8 to test. Thanks a lot, now I won’t have to use a modernizer.
Using this makes the svg scale down in firefox android ( 25 ), which is a shame because for me it was the easiest solution.
I’ve managed to go around it by inlining the svg part like so:
And by this i mean the image tag solution
Solution to the .png load abort issue is here, need to have
Hello,
You can use HTTP Debugger Pro http://www.httpdebugger.com for analyzing the http traffic.
It is proxy-less solution and have zero impact to the transferring data.
Thanks,
Khachatur
Anecdotally, the example:
Does not fallback to PNG on Android 2.3 devices I’ve tested (which presumably support multiple backgrounds but do not support SVG).
UGHACKH. Sad.
You’re right. Android 2.3 DOES support multiple backgrounds but does NOT support SVG (in any way), so it fails. I tested it.
This technique does work for IE 6-8, which is pretty nice still, but a no-image fail in Android 2.3 is fairly bad.
Android is bumming me out lately. Just discovered it’s impossible to detect for proper background-clip support, which suxxx.
Combining multiple background images with linear-gradient seems to get round this and keeps older Android happy.
Credit: pauginer.tumblr.com/post/36614680636/invisible-gradient-technique
I would prefer to use the JS to scrape the DOM and replace the .svg with a .png, but in Rails apps the caching in prod causes this to fail.
For example:
= image_tag "some_image.svg"
In the DOM (in production) you’ll see something like
/assets/some_image-e262ae2535d6490521680316bbe7676e.svg
for browsers that support it. But for the browsers that do not support it, the JS will swap it out exactly but basically replace the .svg with .png like/assets/some_image-e262ae2535d6490521680316bbe7676e.png
.Unfortunately, that will not work. The file
some_image-e262ae2535d6490521680316bbe7676e.png
does not exist.Hi, I try to combine your “using SVG as …” solution with your svg sprite solution (https://css-tricks.com/svg-symbol-good-choice-icons/). But it doesn’t work. Do you know why xlink:href=”svg.svg#id-tag” doesn’t work in image tag but in use tag?
Cheers
Philip
I think I got it. Here is the anwser: http://www.w3.org/TR/SVG/struct.html#ImageElement
“Unlike ‘use’, the ‘image’ element cannot reference elements within an SVG file.”
But how can I use svg sprites with an png fallback?