How many stacked canvas elements is too much?

posted in: Uncategorized | 3

So, after reading an article by Adrian Holovaty (@adrianholovaty), I learned about stacked canvas elements. The idea here is that you can improve performance (and have clean code) by using canvas elements stacked on each other, kind of like layers in Photoshop. This works well since canvas elements are transparent by default, and instead of redrawing an entire scene, you can only redraw specific parts (a character in a game for example). This made me wonder something: at what number of stacked canvas does performance take a hit?

A quick search of Google brought me to a related question on Stack Overflow.

“I imagine you will probably hit a practical performance ceiling long before you hit the hard specified limit of somewhere between several thousand and 2,147,483,647 … depending on the browser and what you’re measuring (number of physical elements allowed on the DOM or the maximum allowable z-index).”

So, when DO we hit a performance ceiling? Should this even be a question? Will we ever need that many canvas elements?

I decided to run a test.

The Test

First, I set up some basic CSS and HTML for my test case.

<style>
#canvasStack		{
	position: relative;
	width: 100px;
	height: 100px;
}

canvas		{
	position: absolute;
	top: 0px;
	left: 0px;
}
</style>
<div id="canvasStack"></div>
<div id="canvasCount"></div>
<div id="canvasTime"></div>

Step 2, requestAnimateFrame shim by Paul Irish (@paul_irish).

window.requestAnimFrame = (function(){
  return  window.requestAnimationFrame       ||
          window.webkitRequestAnimationFrame ||
          window.mozRequestAnimationFrame    ||
          function( callback ){
            window.setTimeout(callback, 1000 / 60);
          };
})();

And now the bulk of the code…

var canvasStack = document.getElementById('canvasStack');
var canvasCount = document.getElementById('canvasCount');
var canvasTime = document.getElementById('canvasTime');
var count = 0;
var canvasArray = [];
var maxtime = 0;
canvasTime.innerHTML = "Max Time to Animate: "+maxtime;

(function animloop(){
	//Start
	var start = new Date().getTime();
	
	//Create New Canvas
	var canvas = document.createElement('canvas');
	canvas.width = 200;
	canvas.height = 200;
	canvasStack.appendChild(canvas);
	canvasArray.push(canvas);
	
	//Do Operations on each canvas
	for(i=0;i<canvasArray.length;i++){
		canvas = canvasArray[i];
		var context = canvas.getContext('2d');
		context.strokeStyle = "#000000";
		context.fillStyle = "#FFFF00";
		context.beginPath();
		context.arc(51,51,50,0,Math.PI*2,true);
		context.closePath();
		context.stroke();
		context.fill();
	}
	
	//End
	var end = new Date().getTime();
	
	//Add to Canvas Count
	count++;
	canvasCount.innerHTML = "Canvas Elements Created: "+count;
	
	var diff = end - start;
	if(diff > maxtime){
		maxtime = diff;
		canvasTime.innerHTML = "Max Time to Animate: "+maxtime;
	}
	
	if(diff < 100){
		requestAnimFrame(animloop);
	}
})();

In short, the code creates canvas elements, adds them to an array, and also adds them to the canvasStack div on the page, stacked on top of each other.

For each added canvas, a filled circle is drawn on EVERY canvas.

This operation is timed, and the max time to complete the operations is kept track of, as well as number of canvas elements on the page.

The Results

Something I never considered, through I should have known, RequestAnimateFrame slows down in all three browsers I tested (Chrome 26, Firefox 20, IE10). However, they each slow down differently, and have different effects on the max draw time.

Chrome
chrome
So, at 1000 canvas elements stacked on each other, the max time to complete the operations was 89 milliseconds. Interestingly, while the draw time increased, the frame rate of request animate frame stayed pretty fast throughout the test, but started to slow down at around 800 canvas elements. It wasn’t till about the 1000 mark that it began to crawl.

Firefox
firefox
Firefox’s max draw time only got to 24 milliseconds at 1000 canvas elements, however its RequestAnimateFrame rate seemed to be slower than Chrome’s, and slowly down considerably around 500 canvas elements.

Internet Explorer
ie
IE had similar results to Firefox, hitting 32 milliseconds max draw time at 1000 canvas elements. RequestAnimateFrame slowed down around 500 canvas elements, and got very slow around 700.

Conclusion

It appears Chrome disagree’s with Firefox and IE when it comes to RequestAnimateFrame frame rate, which I would assume is something in the way the browsers handle it. Perhaps someone could educate me on these results.

Regardless, even at 89 milliseconds, user’s probably around going to notice the slowed redrawing. On the same thought, are you ever going to have 1000 canvas elements?

In conclusion, I personally wouldn’t ever worry about the performance ceiling of stacked canvas elements.

Sources:
http://stackoverflow.com/questions/7973591/maximum-number-of-canvases-used-as-layers
http://paulirish.com/2011/requestanimationframe-for-smart-animating/

My name is Andrew McGivery. I currently work full time as an application developer at Manulife Financial in Canada. My current passion is building and leading highly engaged teams where employee happiness, learning, and growth is a priority.

3 Responses

  1. Not sure how much of a difference it’ll make, but might be an idea to use window.performance instead of Date() http://gent.ilcore.com/2012/06/better-timer-for-javascript.html

    • Might be something to look into. Probably a few optimizations I could have made to the test, but it was more of a quick and dirty proof of concept.

  2. Michael

    Hey really cool test thanks for that. I’m currently playing around with canvas and was considering using multiple canvas elements. Let’ say you use one canvas for the background one for the entities and one for the ui. I think you can get a huge performance gain because you don’t have to repaint the whole background when walking over it.

    Also regarding performance.now() I think its not worth taking it you may get a more accurate number through it but Date.now() is way faster.

    Someone should put that test on jsperf.org btw 🙂

Leave a Reply