I’m writing a book on HTML5, including Canvas! Click here for more information.
I have in my possession sufficient evidence for the conviction of Canvas’ fillText().
The charge? Performance murder.
fillText() resisting arrest
I suspected fillText() of being slow ever since adding blocks of text as interactive objects to a Canvas library I am working on. I’ve also begun writing a canvas performance guidebook, so the sacrament of confirmation was doubly in order.
Since most of the slowness in canvas applications isn’t drawing something once, but redrawing it many times per second, I wondered if there were a faster way to redraw text than using the Canvas context’s fillText() method.
This is especially pertinent when drawing vertical text, since every new letter has to be on another line, and thus another call to the fillText() method.
I puzzled around with ways to avoid all the calls, and settled on trying something: Instead of calling fillText() to redraw my text objects each frame, I would instead create a new canvas (one never added to the DOM) for every single text object, and call fillText() on each object only once, drawing the text to its personal canvas.
Then, every time I wanted to (re)draw that text object, I would call drawImage() on my real canvas, passing in the object’s personal canvas, instead of using fillText(). This gives exactly the same per-pixel drawing, but is it faster?
I wrote a small, distilled test for the purpose, hypothesizing that it probably wasn’t worth doing for normal text but may be worth doing for vertical text. There’s a link at the end if you’re interested in trying it yourself.
There are a lot of scenarios though: What if I had just one text object that had to be redrawn 60 times a second? Surely – if drawImage() is faster than fillText() – having an additional canvas would be a small price to pay in overhead.
But what if I had 200 text objects? Maybe each text object having a canvas would cause it to be far slower.
I won’t bore you much longer, the definitive answer is this: If you are redrawing several text objects more than just a few times, it is far faster to give each object its own canvas and use drawImage() instead of fillText(). Not just for vertical text, but for any text, even if its just drawing a single character.
Some of the tests are described below in my poorly-made graphs. The first row of graphs are closer to real-use scenarios. Please note the Y-axis range (milliseconds) changes from graph to graph.
As you can see, drawing a text object of just 1 character several hundred times is far faster in Chrome, Firefox and, IE9 when using drawImage(), and thus very much worth it to have that extra canvas around.
The second row of graphs were some fooling around to find the limit of this method’s utility. If you have 200 text objects (and thus 200 additional canvases) of just 1 character each, and render them only once, using drawImage() becomes a bit of a waste. But drawing those 200 objects merely three times and you can see that it will be faster in chrome to use drawImage() than to use fillText(). Firefox and IE9 are a little more resilient here, but they don’t take very long at all for drawImage() to become worth it.
So giving each text object its own Canvas is a pretty workable solution even if the objects are redrawn very few times. If you are trying to achieve 30 frames per second, giving each text object its own Canvas and using drawImage() is a no-brainer, and should probably a default consideration.
Of course Canvas applications can differ wildly, and you should draw up tests for your own situation before deciding how to draw text. Perhaps exceptionally large canvases or exceptionally small text yield different results, or perhaps memory on the targeted machine means that so making so many canvases are an untenable proposition, and so on.
Here’s a link to the test I made. I would of course appreciate any corroboration of results that anyone is willing to do.
You’d need to do more work to get this to work with scaling text, since you’d have to size the temporary canvases appropriately. It does however have the added benefit of fixing the scaling + rotating text bug that currently exists in Chrome and Opera.