Grow your CSS skills. Land your dream job.

SVG Fallbacks

Published by Chris Coyier

If you like, you may skip straight to the fallbacks.

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.

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!


Wanna learn more about SVG?

I have a full course available called Everything You Need to Know about SVG that covers the whole spectrum of SVG from the perspective of a web designer and front end developer.

Comments

  1. The PNG ends up with a result of “Aborted” and listed as 0B received, but still effects the download timing.

    affects.

  2. Scott Jehl
    Permalink to comment#

    +1 to Mat’s point. I couldn’t fit that in the tweet :)

  3. Permalink to comment#

    There is another problem with IE´s and image used as css selector:
    The IE´s alias the selector ‘image’ to ‘img’.
    Never mind that the styles are in the head or separate css file:

  4. 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.

        if (!Modernizr.svg) {
            $('img[src$=svg]').each(function(index, item) {
                imagePath = $(item).attr('src');
                $(item).attr('src',imagePath.slice(0,-3)+'png');
            });
        }
    
    • Nice technique, Steve. I use a similar approach:

      if(!Modernizr.svg) {
        $('img[src*="svg"]').attr('src', function() {
          return $(this).attr('src').replace('.svg', '.png');
        });
      }
      
  5. 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.

  6. Permalink to comment#

    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.

  7. 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:

    <svg width="143" height="27">
        <image xlink:href="http://s.cdpn.io/3/svg.svg"  width="143" height="27" />
        <!--[if (lte IE 8)|(!IE)]><image src="http://s.cdpn.io/3/svg.png"  width="143" height="27" /><![endif]-->
    </svg>
    

    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

      <svg width="143" height="27">
        <!--[if (lte IE 8)|(!IE)]>
        <style>.svg {display: none;}</style>
        <image src="http://s.cdpn.io/3/svg.png"  width="143" height="27" />
        <![endif]-->
        <image class="svg" xlink:href="http://s.cdpn.io/3/svg.svg"  width="143" height="27" />
      </svg>
      

      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.

  8. Permalink to comment#

    Some nice thoughts going in here for sure. Love the fact. Although, I hope there would be better options available in the future

  9. Wonderful work, all of you.

    That requires HTML though, so if that’s not possible or practical for 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).

  10. As noted, the switch and foreignObject 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.

  11. Well I’m pretty sure we don’t have to worry about iOS3 or 4 anymore since their slice of the iOS pie is quite small to almost nothing.

  12. Bah, I mean “a cache hit”.

  13. 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.

  14. Johnslegers
    Permalink to comment#

    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 :

    <a href="index.html" class="logo"></a>
    

    CSS :

    .logo {
        background-image: url('../img/bookmark.png');
        background-image: none,url('../img/bookmark.svg');
        width: 59px;
        height: 120px;
    }
    
  15. 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:

    <a href="#">
        <svg width="96" height="96">
          <image xlink:href="svg.svg" src="svg.png" width="96" height="96" />
        </svg>
    </a>
    

    CSS:

    a svg { vertical-align:top; }
    
  16. Permalink to comment#

    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!!

  17. Mathew Eis
    Permalink to comment#

    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.

  18. Miles Elam
    Permalink to comment#

    What not have the object tag use its own fallback mechanism and skip the Modernizr+CSS step?

    <object type="image/svg+xml" data="image.svg">
      <img src="image.png">
    </object>
    
    • Patrick
      Permalink to comment#

      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.

  19. Martin
    Permalink to comment#

    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:

    <svg xmlns="http://www.w3.org/2000/svg" width="30" height="30"><rect x="7.124" y="3.206" transform="matrix(.707 -.707 .707 .707 -3.995 9.887)" fill="#999" width="5.622" height="13.119"/><polygon fill="#666" points="25.75,15.081 25.75,3.15 13.636,3.188 17.729,7.168 14.412,10.413 10.75,14.694 10.75,25.167 18.75,25.167 18.75,15.588 22.458,11.042"/><image alt="asd" src="assets/images/gmaps.png" alt="asd" width="31" height="29"/></svg>
    
  20. Martin
    Permalink to comment#

    And by this i mean the image tag solution

  21. Permalink to comment#

    Solution to the .png load abort issue is here, need to have

  22. 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

  23. Anecdotally, the example:

    .my-element {
      background-image: url(fallback.png);
      background-image: url(image.svg), none;
    }
    

    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.

  24. Jarod Taylor
    Permalink to comment#

    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.

  25. Philip
    Permalink to comment#

    Hi, I try to combine your “using SVG as …” solution with your svg sprite solution (http://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

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".