«

»

May 07

Stratiscape – A layered approach to HTML5 Canvas drawing

When I created my first HTML5 game I was surprised that the canvas element in HTML5 didn’t support multiple layers that could be painted on independently. Independent stacked layers would help in many situations when animating only a portion of a game (like the main character) so that the system doesn’t have to repaint backgrounds or other elements that remain mostly static. One way to get around this limitation is to have multiple stacked canvas elements that are painted independently and serve the same layered functionality I wanted. There are some canvas libraries that already support layers, but many of them were very heavy and added much more functionality than I needed for my game. That’s when I decided to build Stratiscape, a simple HTML5 layered canvas library.

Using the Stratiscape library is straightforward, it is a matter of creating an instance of the main Stratiscape class, defining one or more drawn objects for your canvases and then adding those objects to a stratiscape layer, and finally setting up the drawing interval. This article will show you how to build a simple animated stratiscape page, in the background we’ll draw a blue rectangle, and in the foreground we’ll have some circles that will bounce around inside the rectangle.

To create an object of the Stratiscape class, we pass in a config object that defines how we want the layers created:

document.stratiscapeDraw = new Stratiscape({'containerId':'canvasContainer',
		'layers':[
			{'name':'canvasBackground', x:0, y:0, width:640, height:480, 'backgroundColor':'black'},
			{'name':'canvasForeground', x:0, y:0, width:640, height:480}
		]
	});

In our config object, one of the required properties is ‘containerId’ which should be the id of the element we want all of our generated canvas elements to reside, so we’ll want to make sure our page has a div with an id that we specified in our config object: “canvasContainer” like this:

Next let’s figure out what we want to draw and how we’ll draw it, to do this in Stratiscape, we define a subclass of the DrawnObject class and we should at least define two methods init and draw.

Here is a simple example of a “Rectangle” drawn object, the init function takes some position and size parameters as well as a couple of colors to use when we draw it.

Rectangle = Stratiscape.DrawnObject.extend({ //rectangle drawn object class

	init: function(x, y, width, height, fillColor, strokeColor) {
		this.x = x;
		this.y = y;
		this.width = width;
		this.height = height;
		this.fillColor = fillColor;
		this.strokeColor = strokeColor;
	},
	
	draw: function(ctx) {
	
		ctx.fillStyle = this.fillColor.toString();
		ctx.strokeStyle = this.strokeColor.toString();
		ctx.fillRect(this.x,this.y,this.width,this.height);  
		ctx.strokeRect(this.x,this.y,this.width,this.height);  
	}
});

Notice the draw method is passed a “ctx” object, which is the 2d context for the layer (canvas) that the Rectangle is a child of.

In order to make our stratiscape object actually draw, we’ll need to periodically call the main draw method on the stratiscape object like this:

window.setInterval(function() {stratiscapeDraw.draw() }, 1000 / 60);

Now all we have to add instances of our Rectangle Drawn Object to a layer of our stratiscape object and the library will take care of the rest.

document.stratiscapeDraw.getLayer('canvasBackground').addChild(new Rectangle(10, 20, 200, 150, "#369", "#FFF"));

The results of this are a bit uninteresting:

Let’s make our sample a bit more exciting and leverage the fact that we have two independent layers, lets make another class inheriting from drawn object, a circle:


Circle = Stratiscape.DrawnObject.extend({ //circle drawn object class

	init: function(x, y, radius, fillColor, strokeColor, velocity, angle) {
		this.x = x;
		this.y = y;
		this.radius = radius;
		this.fillColor = fillColor;
		this.strokeColor = strokeColor;
		this.velocity = velocity;
		this.angle = angle;
	},
	
	draw: function(ctx) {
	
		ctx.strokeStyle = this.strokeColor;
		ctx.fillStyle = this.fillColor;
		ctx.lineWidth = 1;
		ctx.beginPath();
		ctx.arc(this.x,this.y,this.radius,0,Math.PI*2,true);
		ctx.closePath();
		ctx.fill();
		ctx.stroke(); 
	}
});

Notice that we’ve given the circle class a velocity and angle, this is because we want to have our circles bouncing around our canvas layer. Let’s add an update method to the circle class that we’ll call repeatedly to change the circle’s location based on the angle and velocity.

    update: function() {
		var offsetX = 0;
		var offsetY = 0;
		if(this.velocity != 0)
		{
			offsetX = this.velocity * Math.cos(this.angle * Math.PI/180);
			offsetY = this.velocity * Math.sin(this.angle * Math.PI/180);
		}
		
		this.x -= offsetX;
		this.y -= offsetY;
		
		//check for collisions with our rectangle
		var top = 15 + this.radius, left = 20  + this.radius, bottom = 465 - this.radius, right = 620 - this.radius;
		if(this.x <= left) {
			this.x = left;
			this.angle += this.angle < 90 ? 90 : -90; 
		} else if(this.x >= right) {
			this.x = right;
			this.angle += this.angle > 180 ? 90 : -90; 
		} else if(this.y <= top) {
			this.y = top;
			this.angle += this.angle > 90 ? 90 : -90;
		} else if(this.y >= bottom) {
			this.y = bottom;
			this.angle += this.angle > 270 ? 90 : -90;
		}
		
		if(this.velocity <= 0)
			this.layer.removeChild(this);
			
		if(this.angle > 360)
			this.angle = this.angle%360;
		if(this.angle < 0)
			this.angle = 360 + this.angle;
	},

Now all we have to do is add some of these circles to our foreground layer and setup our interval to call the update method on each circle.

document.canvasForeground = document.stratiscapeDraw.getLayer('canvasForeground');
		
for(var i = 9; i >= 0; i--)
{
	var radius = (i + 1) * 5;
	document.canvasForeground.addChild(new Circle( 100 + radius, 75 + radius, radius, "#008000", "#FF0", 100/radius, (45 + (i * 90))%360));
}

setInterval(function() 
		{
			for(var i in document.canvasForeground.children)
			{
				document.canvasForeground.children[i].update();
			}
			document.canvasForeground.needsDisplay = true;//set "dirty" flag
		}, 1000/30);

Notice that after calling the update method on each of the foreground layer's children, I need to set the "needsDisplay" flag to true on the canvas layer. This tells Stratiscape that changes have been made to the layer and that it must be re-drawn. Stratiscape will set the needsDisplay flag for you whenever adding or removing a drawn object from the layer, however, because we are just moving our objects around we must explicitly tell the layer that updates have been made and that it should re-draw itself on the next draw cycle. We could repaint a simple scene like this as often as we like and we would likely not see any performance degradation. However, for large, complex scenes where repainting might be more intensive, only drawing when objects have changed is an important optimization to make games and animation run smoothly.

Here is our full simple sample in action:

Download the full source for this sample here: Stratiscape simple sample
Checkout the other Stratiscape library samples and download the full source from github here:
Stratiscape - Layered Canvas Library