Categories
Articles

HTML5 Canvas Circular Rotation Animation Example Using JQuery Hotkeys For User Interaction

This takes my first example animating a filled circle rotating around the center point to a user interaction level using JQuery Hotkeys. JQuery Hotkeys is a plug-in that lets you easily add and remove handlers for keyboard events anywhere in your code supporting almost any key combination using JQuery.

Foundation HTML5 Animation with JavaScript
Learn More

This version of the animation allows for start and stop playing. It also includes reversing the direction of rotation from clockwise to counter clockwise and back again. Finally I decided to allow increasing and decreasing the speed of rotation.

Another change from the previous example is to move as much relevant functionality as possible into the animated object called player. For example the player object is responsible for its own data computations and for interpreting key events.

The events in the animation are the timer and key events. The timer event calls the update and draw functions at the desired frame rate. The update and draw functions then call the same functions for all animation objects: we only have one animation object at this point.

Demo

Download

[ad name=”Google Adsense”]
This is the html5 head section. Line 4 brings in JQuery from the Google CDN. Line 5 references a local copy of JQuery Hotkeys. In this case the 0.7.9 minimized version.

The html structure is expanded to include elements for a main title, a subtitle and keyboard instructions for the animation. The internal style on lines 9 – 27 provide formatting for these new structural elements.

<!DOCTYPE HTML>
<html>
<head>
<script language="javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js" type="text/javascript"></script>
<script src="js/jquery.hotkeys-0.7.9.min.js"></script>
<title>HTML5 Canvas Based Animation Circular Movement Start, Stop, Reverse, Faster, Slower</title>
<meta charset="UTF-8">
<title>Untitled Document</title>
<style type="text/css">
	body {
		font-family:Arial, Helvetica, sans-serif;
		font-size:12px;
	}
	#titleDiv{
		font-family:Verdana, Geneva, sans-serif;
	}
	#titleHeadDiv{
		font-size:16px;
	}
	#titleSub1Div{
		font-size:14px;
	}
	#instructionsDiv{
		margin-top:10px;
		margin-bottom:5px;	
	}
</style>
</head>
<body>
<div id="titleDiv">
    <div id="titleHeadDiv">HTML5 Canvas Based Animation Circular Movement</div>
    <div id="titleSub1Div">Key Controlled Start, Stop, Reverse, Faster, Slower</div>
</div>
<div id="instructionsDiv">Keys: P=Play, S=Stop, R=Reverse, Up=Faster, Down=Slower</div>

<script type='text/javascript'>

To keep the code readable, lines 38 – 57 define constants.

Line 54 – 68 are dynamically appending the canvas element to the body element. Then the canvas variable on line 61 represents the drawing context for the code.

// Canvas width
	var CANVAS_WIDTH = 480;	
	// Canvas height	
	var CANVAS_HEIGHT = 480;	
	// Rotation direction constants
	var CLOCKWISE = 'clockwise';
	var COUNTER_CLOCKWISE = 'counter_clockwise';
	// Key name constants
	var KEY_NAME_PLAY = 'p';
	var KEY_NAME_STOP = 's';
	var KEY_NAME_REVERSE = 'r';
	var KEY_NAME_FASTER = "up";
	var KEY_NAME_SLOWER = "down";
	// Constants to match JQuery Hotkeys event value type.
	var KEY_TYPE_UP = "keyup";
	var KEY_TYPE_DOWN = "keydown";
	var FPS = 30;				// Frames per second
	// Canvas center points adjusting for stroke.
	var canvasCenterX = (CANVAS_WIDTH - 1) / 2;
	var canvasCenterY = (CANVAS_HEIGHT - 1) / 2;
	// Create a canvas element variable.
	var canvasElement = $("<canvas width='" + CANVAS_WIDTH + 
	  "' height='" + CANVAS_HEIGHT + "'></canvas");
	// Reference to the canvas 2d context.
	var canvas = canvasElement.get(0).getContext("2d");
	// Dynamically append a canvas element to the body tag.
	canvasElement.appendTo('body');	
	// Object defining the player we are rotating.

[ad name=”Google Adsense”]
The player object is the bulk of the code to study.

Lines 65 – 77 define a series of properties for the player object. The player simply rotates around a point.

This example aimed at making the animation objects more independent. For example the speed property allows an independent speed.

	var player = {
		color: "#00A",
		x: 0,  		// Starting x
		y: 0,		// Starting y
		radius: 20,	// Radius of the player
		direction: CLOCKWISE,	// CLOCKWISE or COUNTER_CLOCKWISE
		angle:0,				// Degrees of rotation for sin and cosine
		speed: .05,				// Pixels per frame
		speedChange: .001,		// Pixels per frame
		speedMax: .18,			// Pixels per frame
		speedMin: .005,			// Pixels per frame
		radiusRotation:200,		// Rotation circle radius
		isPlaying: false,		// True false state animating

The player draw method is called from the main animation loop. The method uses the player object’s properties to draw the player. One bare number for lineWidth could have been added to properties.

		draw: function() // Draw the player.
		{
			canvas.fillStyle = this.color;		
			canvas.beginPath();  
			canvas.arc(this.x,this.y,this.radius,0,Math.PI*2,true); 
			canvas.fill();
			canvas.lineWidth = 5;
    		canvas.strokeStyle = "black";
    		canvas.stroke();
	  	},

The init method for the player object computes the starting x and y positions. The main animation init function calls the player init method.

		init: function()
		{
			this.x = canvasCenterX + Math.cos(this.angle) * this.radiusRotation;
			this.y = canvasCenterY + Math.sin(this.angle) * this.radiusRotation;
		},

Handling keyboard input is central to this example. The main code captures the key input using JQuery Hotkeys. There the player object keyEvent method is called. It receives the keyType representing either a key down or key up state and the name of the key.

The key down state is not checked but is the state for the play, stop, faster and slower key names. The key down state causes JQuery Hotkeys to repeat the key while it is down. This is acceptable for the play and stop key although we might want to limit those to a key up only state as you will see we did for the reverse key. The faster and slower keys we want to accept repetition if they are held down as we would with an accelerator or brake.

For the reverse key, the key up state is used. If the key down state was used, the reverse key would repeat creating a chaotic result for direction.

		keyEvent: function(keyType, keyName)
		{
			// Set to playing state.
			if (keyName == KEY_NAME_PLAY)
			{
				this.isPlaying = true;
			}
			// Set to stopped state
			else if (keyName == KEY_NAME_STOP)
			{
				this.isPlaying = false;
			}
			// Increase speed if playing
			else if (keyName == KEY_NAME_FASTER && this.isPlaying)
			{
				this.speed += this.speedChange;
				this.speed = Math.min(this.speedMax, this.speed);
			}
			// Decrease speed if playing
			else if (keyName == KEY_NAME_SLOWER && this.isPlaying)
			{
				this.speed -= this.speedChange;
				this.speed = Math.max(this.speedMin, this.speed);
			}
			// Up state of the key.
			if (keyType == KEY_TYPE_UP)
			{
				// Change from clockwise to counter clockwise rotation
				if (keyName == KEY_NAME_REVERSE && this.direction == CLOCKWISE)
				{
					player.direction = COUNTER_CLOCKWISE;
				}
				// Change from counter clockwise rotation to clockwise rotation
				else if (keyName == KEY_NAME_REVERSE && this.direction == COUNTER_CLOCKWISE)
				{
					player.direction = CLOCKWISE;
				}
			}			
		},

The update method handles computation of the model data for the player object. Assuming the player is moving, identified by the isPlaying property, the new position on the rotation perimeter is computed using standard trigonometry.

Also the direction is handled by simply incrementing or decrementing the angle property.

		update: function()
		{
			// Update player data model if playing.
			if (this.isPlaying)
			{
				// Compute the triangle coordinates from the center of rotation
				player.x = canvasCenterX + Math.cos(this.angle) * this.radiusRotation;
				player.y = canvasCenterY + Math.sin(this.angle) * this.radiusRotation;
				// Keep moving the rotation clockwise.
				if (this.direction == CLOCKWISE)
				{
					this.angle += this.speed;
				}
				// Keep moving the rotation counter clockwise.
				else if(this.direction == COUNTER_CLOCKWISE)
				{
					this.angle -= this.speed;
				}
			}
		}
	};	

This is the main animation init function. The task is to initialize all components. The JQuery Hotkeys are initialized here. The player object has its own init method.

The JQuery Hotkeys for the game are defined here. This is the syntax to listen for key events. Notice the distinction for listening to key down and key up states for each key. As mentioned above the key down states repeat so were useful for all but the reverse key where the key up state was captured.

	// Initialize game
	function init()
	{
		// Set keys for play
		$(document).bind(KEY_TYPE_DOWN, KEY_NAME_PLAY, keyEvent); 
		$(document).bind(KEY_TYPE_DOWN, KEY_NAME_STOP, keyEvent); 
		$(document).bind(KEY_TYPE_UP, KEY_NAME_REVERSE, keyEvent); 	
		$(document).bind(KEY_TYPE_DOWN, KEY_NAME_FASTER, keyEvent); 
		$(document).bind(KEY_TYPE_DOWN, KEY_NAME_SLOWER, keyEvent); 
		player.init();
	}

[ad name=”Google Adsense”]
The init function registers the keyEvent function for the keys JQuery Hotkeys processes.

We do a bit of conversion of the received event information to make it more readable to our code by creating a key name literal from the keyCode event property.

The final step is to call the keyEvent method on our only player object and it handles the key information accordingly.

	// Evaluate key input
	function keyEvent(evt)
	{
		// Animation name for the key pressed assumed to be the char name.
		var keyName = String.fromCharCode(evt.keyCode).toLowerCase();
		// Up arrow key
		if (evt.keyCode == 38)
		{
			keyName = KEY_NAME_FASTER;	
		}
		// Down arrow key
		if (evt.keyCode == 40)
		{
			keyName = KEY_NAME_SLOWER;	
		}
		// Call animation object keyEvent methods.
		player.keyEvent(evt.type,keyName);
		
	}

The main update and draw functions are called with the animation timer.

The update function recomputes the model data. In this case there is just updating the player model data.

For the draw method, the canvas draw operations are included and then the player draw method is called.

The separation of these two allows for a different timing solution that would separate the processing timing of the data and the drawing.

	// Update the model data.
	function update() 
	{  
		// Call the animation object update events.
		player.update();
	}
	// Draw the views
	function draw() 
	{
		//Canvas object clear and draw background and border.
		canvas.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.strokeStyle = "red";
		canvas.strokeRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		canvas.fillStyle = "#ccc";
		canvas.fillRect(.5, .5, CANVAS_WIDTH-1, CANVAS_HEIGHT-1);
		// Call the animation object update events.
		player.draw();
	}

This is were the animation starts. Once JQuery fires its ready function, the animation init method is called and then a timer interval is calls the update and draw functions to drive the animation.

	// JQuery ready
	$(function() 
	{
		// Start here.
		
		// Initialize components
		init();
		// Timer for animation.
		setInterval(function() 
		{
			update();
			draw();
		}, 1000/FPS);
			
	});

</script>
</body>
</html>