HTML5 Canvas fillText considered harmful

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.


Reig-05
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.

Six of the tests. Smaller bars are better.

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.

EXTRA CREDIT:

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.

  • Chris Ryland

    Drawing vertical text should be no different from drawing horizontal text, or text at any angle, if you use the proper transformations…

  • http://www.simonsarris.com simonsarris

    You cannot get the effect of drawing text in a vertical column:

    T
    E
    S
    T

    Like that, with transformations alone.

  • http://twitter.com/b_garcia Bruno Garcia

    I came to the same conclusion when playing with drawText. In my 2D engine, I also tried a system where text sprites are cached to a canvas, but I found some major showstoppers with this approach:
    - There’s no way to accurately calculate the bounding box for that cache canvas. measureText() will tell you the width but not the height.
    - If you call drawText() using a custom CSS3 font that hasn’t been fully downloaded yet, it’ll use the default font instead. There’s no way to tell which font was actually used to draw, and no way to be notified when a font finishes downloading.

    Because of this (and more subtle quirks across browsers) it might be best to use a bitmap font, and blitting each character yourself. Especially for something like games, where consistent presentation is important.

    Interesting observation that drawImage gets faster with the number of calls. Makes sense that images get cached in VRAM only after a certain point.

  • Anonymous

    Thanks for blogging those results. It’s something I’ve always know about, and now having those metrics is going to make a couple of decisions easier. :-)

  • http://www.simonsarris.com simonsarris

    Yeah, the poor measureText can really be a pain. The spec writer (Ian Hixie) has stated that its going to remain that way for a while, too.

    In apps where all the fonts and sizes are known ahead of time I have taken to pre-computing a lot of the related widths and heights.

  • http://profiles.google.com/mostawesomedude Corbin Simpson

    This is an unfortunate truth: Text is slow to draw. You have rediscovered the glyph cache, a traditional technique for accelerating text rendering seen in most windowing systems. I wonder what kind of glyph drawing is going on in browsers that bypasses that cache which is typically present.

    Edit: Man, this post was non-obvious to make.

  • Mike

    Interesting to see how IE9 performs regardless of technique used.

  • Janghang

    This is wonderful information for me. I was developing some web apps displaying text on HTML5 Canvas. I am also a graduate of RPI.

  • http://adrusi.com/ Adrian Sinclair

    How about redefining drawText for the context to automatically cache the text for a given set of properties and use drawImage if there us a cache version

  • Lindsley

    I’ve made a couple of canvas tools that might be of interest to visitors here.  One is a Font creator that make bitmap fonts that then load right into the canvas page.  The other is called Canvas Path Maker.  It creates simple paths and shapes, and then exports out as JS.  They’re both free!   Please see them at
    http://www.davidlindsley.com/canvastools.htm

  • Pingback: » Raster Text In HTML5 Canvas Chase B. Gale

  • Pingback: Swarm Intelligence in HTML5 Canvas | cmikavac.net

  • François-Xavier Thomas

    And apparently this hasn’t changed in 2 years. I just found your blog article while looking for a way to accelerate text rendering on my canvas. Glyph cache is at 19M according to Chrome’s profiler — rendering text along a path can do that — and text rendering is usually more than half the rendering time…

  • DLev

    In my experience with canvas I’ve been disappointed with the quality of rendered text. Oddly IE seems to have the best looking text. Firefox has some odd kerning problems and chrome suffers from bad anti-aliasing (especially with larger fonts). Have you found any best practices to get better text quality?

  • http://www.facebook.com/profile.php?id=527652482 Hjoaco Mg

    excellent.