How you clear your HTML5 Canvas matters

Stop the press! As seen in jsperf, the nightly build of Chrome (14) has optimized the width-setting case and now swings heavily the other way. As with all optimizations, be sure to bench often on the platforms and browser versions you are targeting.

I’m considering writing a small (e-)book on Canvas performance issues, considerations and tips. If you’d be interested in that sort of thing, let me know.

As much as we all like to see dramatic performance increases from clever optimizations, getting the best Canvas performance often means scrutinizing every little place in our code. Today we’ll take a look at how canvases are cleared.


Seco-06
A man’s careful search for his receding hairline

One of the ways that is implicitly endorsed in the spec and often used in people’s apps to clear a canvas is this:

canvas.width = canvas.width;

There is of course another common way to clear the canvas using a context method:

ctx.clearRect(0, 0, canvas.width, canvas.height);

They may seem to do the same thing on the surface, but the end difference between the two methods is large: Setting the canvas width to itself not only clears the canvas, it clears all canvas state. This means it clears the transformation matrix, the current clipping region, and all of the following attributes: strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, and textBaseline.

Much of the time clearing above doesn’t matter, except maybe the transformation matrix, because canvas programmers tend to set the relevant properties before they redraw each shape anyway.

The performance difference between the two above methods is also large, often an order of magnitude or more. The context’s clearRect method is much faster than setting the canvas width to itself. I have a jsperf page up here where you can see the results per browser.

Curiously, clearRect is faster on every browser except Safari on Windows, where it is the other way around. I can think of a few possible reasons why that might be the case, but I don’t want to speculate out loud. If someone on a Mac could test Safari for me, I’d be interested to know what it benches.

Back to clearRect. Not all is well all the time when using this method. After all, if your canvas context has anything but the identity transform, there’s a good chance you won’t be clearing the entire canvas. This leads some people to end up erroneously using the width-setting method. Additionally, many people want to clear the canvas but keep their transformation matrix the same. Both of these problems can be fixed in one go, so in the interest of completeness, lets see a safer way:

// I have lots of transforms right now
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
// Will always clear the right space
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.restore();
// Still have my old transforms

Not only does it clear the screen, but it ensures that any existing transformation won’t get in the way, and also allows you to keep that transformation should you need it. If you don’t need the transformation, you can of course remove the calls to save() and restore().

Because of the large performance discrepancy, I tentatively suggest the use of clearRect over setting the canvas width equal to itself, though the canvas width method is still useful for doing a complete reset of the context state. Of course, browser development happens rapidly and you should always test on the browsers and systems you are targeting.

  • http://twitter.com/peterc Peter Çoopèr

    On the book thing, if you write it and it looks in good shape, I’ll surely put it in JavaScript Weekly :)

  • Anonymous

    On Mac Safari, width=width is *much* faster than clearRect. I got 1,046,422 ops/sec on width=width and 17,051 on clearRect.

  • http://claimid.com/strager strager

    Your own jsPerf tests shows results contrary to your own findings.  For example, Chrome 14 seems to have optimized the `canvas.width = canvas.width` case heavily, as it’s an order of magnitude faster than the other options.  In Opera, performance is about equal.  Firefox and IE9 show the behaviour your assert.  Safari seems similar to Chrome 14 (except in Safari 5.1).

    Browsers have different versions, and there are many browsers out there, so generalizing “all browsers except Safari” is misleading.

    Seeing the current trend in optimizations by browser vendors, I would say choose the `canvas.width = canvas.width` method, and special-case using `clearRect` based on the browser UA (or whatever) (mostly for Firefox; Chrome will be updated soon enough anyway).

    Anyway, I would be greatly interested in a book or guide on 2D canvas optimization.  I had not known about the `canvas.width = canvas.width` method, for example, and such a book would document alternatives to solving common problems like that, with jsPerf links for readers to deduce which method to use for their case (as jsPerf results will probably outlive the book itself).

  • http://www.simonsarris.com simonsarris

    You’re right, it is misleading to say only Safari. I definitely agree that jsperf results will outlive at least half of such a book! And no canvas performance book would be complete without the repeated mantra (or caveat) of “Bench on the browsers and platforms you’re targeting.”

    I hadn’t had any Chrome 14 results at the time of writing. All I had tried it on were the stable, beta, and dev channels, and 14 is nightly-only as far as I know (dev is 13 right now). So now for major browsers we have FireFox and IE9 swinging heavily one way, with Chrome 14+ and Safari swinging heavily the other. I’ll amend the post to reflect. Thanks!

  • Pingback: Optimising HTML5 Canvas games | Freelance Front End Web Developer, Surrey & London UK

  • Richard

    > I’m considering writing a small (e-)book on Canvas performance issues, considerations and tips. If > you’d be interested in that sort of thing, let me know.

    I would… :-)

  • Richard

    Also, how does the globalCompositeOperation method of clearing the canvas fare?

    context.fillStyle = ‘rgba(0,0,0,0)’;context.globalCompositeOperation = ‘source-in’;context = canvas.getContext(’2d’);context.beginPath();context.fillRect(-1000,-1000,canvas.width + 2000,canvas.height + 2000);context.fill();

    Is there any point in the above or would it just be better to use .clearRect()?

    Cheers.

  • http://www.simonsarris.com simonsarris

    clearRect seems to be much faster than using fillRect and changing the globalCompositeOperation.

    For the record, the beginPath() and fill() are unnecessary in your code example. fillRect doesn’t use them so they merely create an empty path and fill it (doing nothing).

  • Terry Thorsen

    I noticed a weird quirk with Firefox (led me to your blog). It seems to be maintaining some state information after drawing paths even after using clearRect(). Whatever it is holding on to degrades the performance of my application over several iterations if I use that method to clear the canvas. Chrome does not demonstrate the same problem. Everything works fine and dandy however if I clear using cavas.width=canvas.width. (I’m not doing any transformations).

  • http://www.simonsarris.com simonsarris

    That’s interesting. The canvas is supposed to maintain all sorts of state on normal use.

    Using canvas.width = canvas.width will clobber all of the state: Path and subpath information, the stroke information, and all the other canvas settings. ClearRect() will not do this, which can often be very useful (some things, like setting the font on a canvas context, are actually very slow to do, so there are many cases where you don’t want to erase all the state if you can avoid it)
    Is it possible that you are continuously constructing a very large path of some kind that you’re never drawing or using?

    For the record, save() and restore() will also save and restore all of the context’s state. So calling save(), drawing everything, calling restore(), then calling clearRect() ought to be equivalent (from a state perspective) to doing canvas.width = canvas.width. You can give that a try too and see if its some state that is really the problem or not.

  • Pingback: Улучшаем производительность HTML5 canvas « findwiki

  • Sumerian

    canvas.width=width is a hack. Always rely on the specification and use clearRect.

  • http://www.simonsarris.com simonsarris

    The specification endorses it and actually uses it in one of its examples:

    “canvas.width = canvas.width; // clears the canvas”

  • Terry Thorsen

    Another quirk I just discovered, this time on Chrome. I’m using two canvases. One canvas is my permanent drawing. The second is a temporary canvas for animations which is transparent on top of the permanent canvas. I’ve found on chrome that if I clear the temporary canvas (using width=width) that the screen goes entirely black. But then as soon as I draw something new then it becomes transparent again.

    So here is my clearCanvas() function that so far works on chrome, firefox, ie, safari, opera (note that I create a reference to the context and store it in the canvas for convenience)

    var is_chrome = navigator.userAgent.toLowerCase().indexOf(‘chrome’) > -1;canvas.context=canvas.getContext(“2d”);

    function clearCanvas(canvas){ var w=canvas.width; canvas.width=1; canvas.width=w; if(is_chrome){ canvas.context.fillRect(0, 0, 1, 1); canvas.context.clearRect(0, 0, 1, 1); }}

  • Pingback: Improving HTML5 Canvas Performance | HTML5Zone

  • ketting00

    That wipe the canvas out entirely. How about reusing it for another draw repeatedly.