Adventures in WebKit land

A blog about mobile and desktop front-end

Dealing with SVG images in mobile browsers

When browsing the web with my retina iPad, I often see websites that could have used SVG for their cartoon-like graphics, but used PNG instead. It seems weird, because most likely those images have been created with some vector graphics editor and then exported or converted to bitmap images.

SVG has been supported in most browsers for years, but still it seems that developers are not yet comfortable with using SVG images on their websites.

SVG is quite well-supported in mobile browsers. This means that you can link to a SVG file on your page in most mobile browsers and it just works. But… there is one big problem: Android versions under 3 don’t have any kind of support for SVG in the stock browser. Desktop browsers have a similar situation with older Internet Explorer versions not supporting SVG.

Using SVG on older Android versions

Todd Anglin wrote a very good post on Kendo UI blog a year ago on how to deal with the situation on Android 2.X. In the blog post he describes how you can polyfill Android 2.X’s SVG support by using a javascript library to render SVG on a HTML5 canvas element.

In his post he writes that according to Google’s stats 94% of the Android users use version 2.1, 2.2 or 2.3. Fortunately the situation has improved a lot in a year, and that percentage is ~54% at the time of writing this article. Well, 54% of all Android users is still a huge amount of people!

Polyfilling missing SVG support with Canvas

You can use Modernizr and Canvg together to provide fallback(s) for SVG:

Checking for SVG and Canvas support, polyfilling if needed > JSBin demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// In this example the SVG image is included as a string (mySVGImage),
// you could also link to a file (myimage.svg).

if (!Modernizr.svg) {
  if (Modernizr.canvas) {
      var canvas = document.createElement("canvas");
      canvas.setAttribute("style", "height:500px;width:300px;");
      canvg(canvas, mySVGImage);
      document.body.appendChild(canvas);
  } else {
      // Do something else, perhaps a fallback to PNG?
  }
} else {
  var svgObj = document.createElement('object');
  svgObj.setAttribute('type', 'image/svg+xml');
  svgObj.setAttribute('data', 'data:image/svg+xml,' + mySVGImage);
  svgObj.setAttribute('height', '500');
  svgObj.setAttribute('width', '300');
  document.body.appendChild(svgObj);
}

This is just a basic example, so if you use it, you should add more logic to make the width and height values more flexible, and for example polyfill missing Canvas support or add a fallback to a PNG image.

Polyfilling SVG with Canvas demo

Here is a link to a JSBin example that shows the polyfilling in full action: http://jsbin.com/ujujuw/1/edit

Performance testing SVG to Canvas rendering

When I heard about the canvas rendering technique, I wanted to test if it is actually performant enough, knowing that SVG images might be a bit slow to render on slower mobile devices.

To get a good idea of the performance, the rendering speed needed to be tested with a really slow device. ZTE Blade was perfect for this kind of testing, having Android 2.2, 512Mb of RAM and a CPU with clock speed of only 600mhz. I ran some browser performance tests on it earlier, so I knew that it is slow.

For now I tested the polyfill performance with only one device, but I might revisit the testing with more 2.X devices when I have a bit of extra time.

For a test image I used this SVG image of map of Finland, that was rendered in 300x500 px resolution. I had an empty canvas element appended to body element and used Canvg library to render the stringified SVG image data to the canvas element.

The test results were quite good for a device as slow as ZTE Blade. It would render the SVG map of Finland in 1.3 - 1.4 seconds. What the result means is that you should be able to use this technique to render at least 1-2 SVG images even on slower Android devices without making the user wait for too long.

Getting the SVG image out of Canvas

HTML5 Canvas element usually has a toDataUrl method that allows you to get the image data out of canvas as a data URL. Newer Android versions and iOS support this method, but unfortunately it does not work in Android 2.X stock browser.

I was looking for a workaround for this, and found a javascript library called todataurl-png-js that can be used to polyfill toDataUrl method for PNG images on Android 2.X.

If you read the todataurl-png-js tutorial you might notice this:

It’s slow. There’s just no way a JS implemented method can keep up with a native one. Plus, the PNG format wasn’t created to be fast: it needs two checksums in order to create a working file and neither of these methods is implemented in a browser’s native code.

A quick performance test clearly demonstrates the awful speed of it.

It took 72 seconds on ZTE Blade to:

  1. First render the map of Finland to the canvas element
  2. Then to get the image data URL out of canvas with polyfilled toDataUrl method (this step took over 70 seconds)
  3. Then to create an image with the data URL as source and append it to the test page.

Converting SVG images to @font-face icons

Since SVG on Android is more or less broken (unless you want to ignore over half of Android users), you can use @font-face icons to replace at least some of your SVG images. This is not a great workaround for the problem, because Android 2.1 stock browser, Windows Phone 7 IE9, Opera Mini and some other browsers do not support @font-face. @font-face also only works for SVG images that are “simple shapes”. You can’t use it to render complex images.

Rendering shapes or icons with @font-face has many limitations, but it is still a valid option in many cases.


– Using @font-face fonts has an additional benefit for desktop browsers: old Internet Explorer versions do not support SVG, but support @font-face fonts.

FontCustom

FontCustom is a command line tool that allows you to convert a bunch of SVG files to a @font-face icon font.

Installation on Mac OS X is easy, but you need to have Ruby (comes with OS X) and Homebrew installed first. Install FontCustom by running:

1
2
$ brew install fontforge eot-utils ttfautohint
$ gem install fontcustom

Put your SVG files inside a folder (mysvgfiles in this example) and run fontcustom compile command:

1
$ fontcustom compile mysvgfiles

…and your custom font is generated:

Files generated with FontCustom
1
2
3
4
5
6
7
create  fontcustom
create  ./fontcustom/fontcustom-5c22c2e1aea68f865df308b2953475ff.woff
create  ./fontcustom/fontcustom-5c22c2e1aea68f865df308b2953475ff.ttf
create  ./fontcustom/fontcustom-5c22c2e1aea68f865df308b2953475ff.eot
create  ./fontcustom/fontcustom-5c22c2e1aea68f865df308b2953475ff.svg
create  fontcustom/fontcustom.css
create  fontcustom/fontcustom-ie7.css

Converting SVG to @font-face demo

For the demo I took this silhouette of man SVG image by Nevit Dilme and converted it to a font icon.

When I set CSS font-size to 1000px, I could see some rendering issues with at least desktop Chrome and iOS Safari. Small part of the man’s pipe is cut off. I have not yet had time to investigate why it happens, and if it is something that can be easily fixed.

Take a look at this JSBin demo to see some very simple examples of font icon rendering and usage: http://jsbin.com/ijifev/2/edit

Minifying SVG image files

You might not be aware that the SVG images created by image editors contain a lot of extra data that you don’t need if you want to display the image on a web page.

SVG files, especially exported from various editors, usually contains a lot of redundant and useless information such as editor metadata, comments, hidden elements, default or non-optimal values and other stuff that can be safely removed or converted without affecting SVG rendering result.

– SVGO readme

You also might not be aware that your SVG images can be minified in a bit similar way as your javascript can be. If you minify your javascript, then why wouldn’t you do the same thing for your SVG images? A great tool to do it is called SVGO.

SVGO

To install it you need to have Node.js installed. SVGO can be installed by running:

1
$ npm install svgo -g

Running the tool on the Finnish map SVG image gives quite impressive results by shaving off almost 40% of the original file size:

1
$ svgo FI-LL.svg FI-LL.min.svg
1
2
Done in 95 ms!
40.766 KiB - 38.7% = 24.99 KiB

File size savings are of course smaller when the file is gzipped:

File sizes for the SVG image unminified / minified
1
2
3
4
5
6
7
|---------|-----------|----------|
|         | Unminfied | Minified |
|---------|-----------|----------|
| Default | 41Kb      | 25Kb     |
|---------|-----------|----------|
| Gzipped | 14Kb      | 11Kb     |
|---------|-----------|----------|

The gzipped size for the optimized image is only 11Kb, so it is not much bigger than a PNG equivalent would be. Compared to the PNG version, the SVG image looks sharp on retina screens and you can scale it as much as you want.

SVGO custom configuration file

You can also use a custom configuration file with SVGO (you can use the default configuration file as a template) where you are able disable and enable various compression settings. This is helpful if you want to make sure that the minification does not create any visual differences between the original and minified images.

1
$ svgo --config my_compression_settings.yml FI-LL.svg FI-LL.min.svg

Conclusion (tl;dr)

SVG is definitely used too little on websites despite of its good support on both desktop and mobile browsers.

The lack of SVG support in Android 2.X stock browser can be polyfilled (link) by rendering SVG to HTML5 Canvas element. The performance penalty of it does not seem to be as bad as I first thought.

Canvas element’s missing toDataUrl method in Android 2.X stock browser makes it impossible to get the image out of the canvas element as a bitmap in a performant way. This means that if you don’t want to make the user wait for ages, you can only use the canvas element to show SVG images to the user.

Another workaround for Android 2.X’s lack of SVG support is to convert SVG images to @font-face icons. You can only render simple shapes or icons with it, and @font-face fonts do not work on Android 2.1 stock browser, Windows Phone 7 IE9, Opera Mini and some other browsers (link).

You should be using a minification tool like SVGO to possibly get noticeable file size savings on your SVG images.

Resources

David Bushell has written many good articles about SVG:

Chris Coyier also wrote about SVG:

SVG images can be blurry too, as Simurai points out:

Polyfilling SVG with Canvas:

Different ways of adding SVG to your page: