The issue of using multiple <canvas> elements
A short introduction on why the general advise to use multiple <canvas> layers to speed up your drawing performance is not really true.
--
As you might know, our forthcoming, free Design Tool Gravit, is completely written in HTML5 and Javascript. We’re making extensive use of the <canvas> Element to boost performance and have blind (outch!) relied on the common advise that stacking multiple <canvas> elements on top of each others would improve performance.
At our current architecture, we’re actually stacking quite a bunch of <canvas> elements on top of each other:
- The background canvas to paint pages
- The scene canvas to paint the actual contents
- The guide canvas to paint the grid and other guides
- The editor canvas to paint editors like the selection
- The tool canvas to paint the active tool
Everything went quite smooth so far until I’ve started to use Gravit full-screen on my 27" Apple LED Display in full-size. Suddenly, even simply moving a selection around went quite slow. As there’s not much to render I was confused what might be the cause of this. We’re optimizing everywhere we can by using dirty regions and only repaint necessary areas, including a smart algorithm that can reduce the dirty regions to a minimum.
Coming from a C/C++ background back in the 90's with strong skills in graphics, I’ve soon discovered the cause of the issues. Due the architecture of <canvas> in HTML5, the browser has no chance of discovering the dirty areas when trying to blend the stacked <canvas> elements together whenever a canvas has been modified. I didn’t dig into any of the browser’s code but I heavily doubt that any of them catches all the various canvas calls and tries to only blend the area of change. So after all, was had happend for every single move was that we’ve painted only a few pixels (4x4) in our canvas but the browser had to blend five extremely large canvases together which of course causes a huge lag.
So, my final advise to the multiple layer issue is to completely avoid stacking <canvas> on top of each other as you never now the final screen size of your user’s browser window and thus, can easily be trapped by the issue mentioned. Instead, I do advise to keep multiple canvases in memory but only add one <canvas> to the DOM on which all canvases in memory get manually blend onto by your own code. Doing it this way and utilizing the <canvas>’ drawImage() method, you can really ensure the browser will repaint the smallest area possible.