The Road to HTML5: SVG, Canvas, and faster performance

For the upcoming 4.5.5 release of the Gliffy Confluence Plugin, we improved both performance and consistency of SVG rendering across various web browsers. It was a big step in our HTML5 development. Keep reading to find out how we did it:

Our HTML5 Viewer release was a great milestone for us. It allowed us to see whether we could accurately display our symbols and diagrams using only the canvas api. It was a great proof of concept, but we found that our initial implementation was much too slow for our liking.

Our 4.5.5 release features a much quicker, nimbler, and less resource hungry HTML5 viewer implementation. In this post, we’ll walk you through some of the problems we overcame.

What is Canvas?
If you’re unfamiliar with canvas, the basic idea is that you have a canvas element in the page with a given width and height. This is essentially a blank box that you draw to with javascript. The commands are quite simple: just imagine that you are verbally giving very precise and detailed commands to someone holding a pen. Pick up the pen and move to point. Draw a line to this point. Fill in the drawn path.

How hard is it to convert a flash app to an HTML5 app? Well, from our experience, it is more challenging than it sounds. The canvas api is, at its heart, a very low level HTML5 drawing api. It doesn’t handle things like multi-line text, or dashed lines, or pixel snapping. Heck, it doesn’t even provide simple font metrics for things like text height, or let you query for the current state of the canvas (the matrix transform). While canvas libraries are in development, you may still find yourself solving low level graphics problems, debugging performance issues, and trying to hack your way around annoying browser bugs.

So, why Canvas?
HTML5 actually includes a slew of technology. You may wonder why Gliffy went ahead with something like canvas versus scalable vector graphics (SVG).

To give you a quick overview, SVG is basically XML that describes a scene, and while canvas and SVG are often compared, they are quite different from one another. The most obvious difference is that canvas is a bitmap. This means that it keeps track of all the pixels that it contains and basically what color it should be. Canvas acts a bit like Paint: you can draw a circle to it, but you can’t select the circle again. Canvas simply doesn’t know that a circle was drawn to it — it's all just bits. With that in mind, when you resize a canvas it will get blurry, like an image does, because it is just a bitmap.

SVG is also declarative whereas canvas is procedural, which means that instead of telling someone exactly how to draw a circle, we just say it’s a circle and let the browser sort out the details. SVG is far more human-readable and doesn't require a long list of canvas commands. In fact, our symbols are stored in our codebase as SVGs before they're processed as flash assets.

So why not SVG?
The main reason was inconsistent implementation. Browser support for SVG is still not as good as canvas, even though the 1.1 recommendation is nearly a decade old. Most modern browsers do (thankfully) support basic SVG elements but have not yet implemented some of the more advanced functions.

Another gotcha is data export. While the canvas api has a toDataUrl() method, which allows you to get the drawn canvas bitmap as a base 64 encoded string, there is no such equivalent call for SVG. Converting SVG to to an image would require a server side call, and this means that images may be rendered differently, depending on how good our server implementation of SVG (batik) is compared with a given browser.

The first approach
So, to recap the problem, our symbols were stored in SVG format, but we wanted to use canvas. How did we solve this?

Here’s what we came up with on our first try:

Create an SVG template

Below is an example of our circle symbol template, using the fresh skin:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" preserveAspectRatio="none">
    <defs>
        <linearGradient id="grad2" x1="100%" y1="0%" x2="0%" y2="0%">
            <stop stop-color="" offset="0%"/>
            <stop stop-color="" offset="100%"/>
        </linearGradient>
        </defs>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="" height="" viewBox="0 0 50 50" overflow="visible" preserveAspectRatio="none">
    <path fill="" d="M25,50C11.214,50,0,38.785,0,25S11.214,0,25,0c13.785,0,25,11.215,25,25S38.784,50,25,50L25,50z"/>
    <circle fill="#000000" fill-opacity=".3" cx="25.806" cy="25.918" r="23"/>
    <circle cx="25" cy="25" r="23.148" style="{