SVG Fallbacks

Avatar of Chris Coyier
Chris Coyier on (Updated on )

📣 Freelancers, Developers, and Part-Time Agency Owners: Kickstart Your Own Digital Agency with UACADEMY Launch by UGURUS 📣

UPDATE: Here’s a more comprehensive guide to SVG fallbacks we’ve published.

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!