Canvas star effect

Past entries

As I said in my previous article, I've decided to share a little more code in my blog, so here is a code sample that you may want to check if you're doing some experiments with canvas.

English isn't my native language, therefore I don't think I would be able to write a full tutorial (writing articles is hard enough actually), but sharing some well-commented code shouldn't be a problem.

The effect allows you to make an animation of stars, with multiple depths of stars. This is the same technique I used in PxWars.

Here is a preview:

You can check the code here, or just copy/paste it from the box below:

(function(){
	// Getting the canvas and its context
	// I don't make any verification about whether the browser supports 
	// canvas or not, as it's not the purpose of this code sample.
	var canvas = document.getElementById('canvas');
	if(canvas == null){
		alert('The canvas couldn't be found.');
		return;
	}
	var ctx = canvas.getContext('2d');

	// Creating as much layers a needed
	var layers = [];
	for(var i = 0,alpha = 0, speed = 20 ; i < 4 ; i++, alpha += 1/4,speed += 20){
		layers.push(createLayer(100,2,alpha,speed));
	}

	// Drawing one layer
	function drawLayer(layer){
		// Calculating the offset module the canvas width.
		// On some browsers, using a huge offset can cause some troubles.
		var offset = ~~(layer.offset % canvas.width);

		// This is the tricky part. To create an offset on the layer, 
		// we translate the context as much as needed, and then we
		// draw it at the right position, so the pattern will have a 
		// different origin.
		ctx.save();
		ctx.translate(offset,0);
		
		ctx.fillStyle = layer.pattern;
		ctx.fillRect(-offset,0,canvas.width,canvas.height);
		
		ctx.restore();
	}

	// Making one layer
	function layerCycle(layer,elapsed){
		layer.offset += elapsed * layer.speed;
		drawLayer(layer);
	}

	var lastFrame = Date.now();
	function animationFrame(){
		// Calculating the time elapsed since the last frame was drawn.
		// We have to do this because we don't know what the frame rate
		// will be (we're not using setInterval or anything like that).
		var now = Date.now();
		var elapsed = (now - lastFrame) / 1000;
		lastFrame = now;
		
		// First, we have to clean the canvas. Let's just put a black background.
		ctx.fillStyle = 'black';
		ctx.fillRect(0,0,canvas.width,canvas.height);
		
		// Then, we can update each layer
		for(var i in layers){
			layerCycle(layers[i],elapsed);
		}
	}

	// Now, we can finally start the animation, using the requestAnimationFrame API
	var requestAnimFrame = (function(){
		return  window.requestAnimationFrame       || 
				window.webkitRequestAnimationFrame || 
				window.mozRequestAnimationFrame    || 
				window.oRequestAnimationFrame      || 
				window.msRequestAnimationFrame     || 
				function(callback){
					window.setTimeout(callback,1000/60);
				};
	})();

	// As we need a function to call with requestAnimFrame(),
	// let's just use a simple closure.
	(function(){
		try{
			animationFrame();
			requestAnimFrame(arguments.callee);
		}catch(e){
			// Animation stops at the first error
			alert('An error occured: ' + e);
		}
	})();

	// Creating a layer. A layer will be a canvas pattern that will
	// be used to draw on the main canvas.
	function createLayer(stars,starSize,alpha,speed){
		// To create a layer, we have to create a buffer canvas
		// to draw the stars on it.
		var layer = document.createElement('canvas');
		
		// Using the right dimensions. Note that you could also 
		// use smaller dimensions, but then you would be able
		// to see that it repeats itself.
		layer.width = canvas.width;
		layer.height = canvas.height;
		
		var layerCtx = layer.getContext('2d');
		
		// Drawing the stars on the buffer
		layerCtx.fillStyle = 'white';
		layerCtx.globalAlpha = alpha;
		for(var i = 0 ; i < stars ; i++){
			// We just have to ensure that the stars are not 
			// at the extreme side of the layer.
			// We also need to use integer coordinates to avoid
			// the blurring effect.
			layerCtx.fillRect(
				~~(Math.random() * (layer.width - starSize)),
				~~(Math.random() * (layer.height - starSize)),
				starSize,
				starSize
				);
		}
		
		// Creating the pattern
		var pattern = layerCtx.createPattern(layer,'repeat');
		
		// For layer that will be used, and some parameters
		return {
			pattern : pattern,
			speed : speed,
			offset : 0
		}
	}
})();

There might be many ways to optimize it, or to add new effects, but I'll leave that to you :-)

< Fitting screen in HTML5 games
My Javascript toolbox >