Introduction
This article introduces the reader the HTML5 canvas element. It builds a basic foundation of understanding of core canvas use concepts; canvas creation, coordinates, gradients, text, and drawing on a canvas. It then builds on these core concepts by demonstrating the power of the canvas in HTML5 by walking the reader through the creation of a simple game.
What is canvas?
Simply put, like the name indicates, a canvas is simply a place to draw or paint stuff. The official definition is something like, “a canvas is a resolution dependent bitmap canvas which is used for rendering graphs, game graphics or other visual images on the fly”.
Creating a canvas
A rectangular canvas area is created on an HTML page by using the canvas tag and specifying width and height attributes.
<canvas id="myCanvas" width="800" height="600"></canvas>
Just like other HTML elements a canvas may have styles applied. For example, to create an 80 x 60 canvas with a 1 pixel wide black border:
<canvas id="myCanvas" width="80" height="60"
style="border:1px solid #000000;">
</canvas>
It is key to provide the id attribute for the canvas element. The id will be used in the javascript to reference the canvas.
- Canvas Coordinates, Paths, Text, Gradients, Images
Now that we know how to create the canvas, we need to understand a little about how it is laid out before we start to draw on it.
The upper left corner of the canvas is the origin, and has coordinates of (0,0). X values increase as we go to the right, and Y values increase as we go down the canvas. So given our 80 x 60 canvas example above, the layout is:
Paths
A canvas path in HTML5 is made up of one or more subpaths. A subpath is defined as a straight or curved line between two points on the canvas. The following methods may be used to connect two canvas points to form a subpath; lineTo(), arcTo(), quadraticCurveTo(), and bezierCurveTo().
It is important to note that these methods do NOT actually draw the subpath on the canvas. They add new subpath ending points to the overall path, and the stroke() method actually draws the path to the canvas.
The beginPath() method is provided to indicate the start of multiple subpaths to be combined/treated as a path.
closePath() may be used to “close” the path by drawing a line from the endpoint of the last subpath to the starting point of the first subpath.
Summary of Path Methods
Method
| Description
|
beginPath()
| Begins a path
|
closePath()
| Creates a path from the current point back to the starting point
|
moveTo()
| Moves the path to the specified point in the canvas
|
lineTo()
| Adds a new point and creates a line from that point to the last specified point in the canvas
|
quadraticCurveTo()
| Creates a quadratic curve
|
bezierCurveTo()
| Creates a cubic Bézier curve
|
arc()
| Creates an arc/curve
|
arcTo()
| Creates an arc/curve between two tangents
|
stroke()
| Actually draws the path you have defined
|
fill()
| Fills the current drawing path
|
clip()
| Clips a region of any shape and size from the original canvas
|
isPointInPath()
| Returns true if the specified point is in the current path, otherwise false
|
The following example uses path and curve methods to create a cursive upper case letter “B”:
<canvas id="myCanvas" width="300" height="200" style="border:1px solid #000000"></canvas>
<script>
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
context.beginPath();
context.moveTo(20, 180);
context.lineTo(25, 10);
context.bezierCurveTo(190, -40, 200, 100, 40, 80);
context.bezierCurveTo(190, 40, 250, 250, 45, 180);
context.quadraticCurveTo(230, 200, 250, 120);
context.lineWidth = 5;
context.strokeStyle = 'green';
context.stroke();
</script>
Result:
Text
HTML5 Canvas Text allows you to draw text on the canvas using various fonts, sizes and colors. Text can be drawn using either the fillText() for solid, or stokeText() for outlined. The font and fillStyle properties of the context may be used to control the text font, font size and color.
For example:
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
context.font = "normal 24px Verdana";
context.fillStyle = "#FF0000";
context.fillText("Red Verdana Filled Text", 50, 40);
context.font = "normal 36px Arial";
context.strokeStyle = "#0000FF";
context.strokeText("Blue Arial Stroke Text", 50, 80);
context.font = "normal 24px Courier";
context.fillStyle = "#00FF21";
context.fillText("Green Courier Filled Text", 50, 120);
Result:
Gradients
Create linear gradients using
Context.createLinearGradient(x0,y0,x1,y1);
Where:
x0 is the x coordinate of the start point of the gradient
y0 is the y coordinate of the start point of the gradient
x1 is the x coordinate of the end point of the gradient
y1 is the y coordinate of the end point of the gradient
For example:
var canvas=document.getElementById("myCanvas");
var context=canvas.getContext("2d");
var grd=context.createLinearGradient(0,50,0,0);
grd.addColorStop(0,"green");
grd.addColorStop(1,"white");
context.fillStyle=grd;
context.fillRect(10,10,90,90);
grd=context.createLinearGradient(100,10,190,10);
grd.addColorStop(0,"red");
grd.addColorStop(1,"white");
context.fillStyle=grd;
context.fillRect(100,10,90,90);
var grd=context.createLinearGradient(100,100,190,150);
grd.addColorStop(0,"black");
grd.addColorStop(1,"blue");
context.fillStyle=grd;
context.fillRect(100,100,90,90);
var grd=context.createLinearGradient(100,10,10,10);
grd.addColorStop(0,"red");
grd.addColorStop(0.5,"green");
grd.addColorStop(1,"blue");
context.fillStyle=grd;
context.fillRect(10,100,90,90);
Result:
Note the use of Gradient.addColorStop(stop,color) in the example. The stop is a value between 0 and 1 that represents transition between start and end in the gradient. For example in the final RGB portion of the example we use stop values of 0, 0.5 and 1 to get equal gradient distribution as we transition from right to left.
Circular or radial gradient patterns may be created using context.createRadialGradient(x0,y0,r0,x1,y1,r1) , where:
x0 is the x coordinate of the starting circle
y0 is the y coordinate of the starting circle
r0 is the radius of the starting circle
x1 is the x coordinate of the ending circle
y1 is the y coordinate of the ending circle
r1 is the radius of the ending circle
Just like the linear gradient, we can use the addColorStop() method to set transition colors for the gradient.
For example:
var canvas=document.getElementById("myCanvas");
var context=canvas.getContext("2d");
var grd=context.createRadialGradient(100,100,5,100,100,100);
grd.addColorStop(0,"red");
grd.addColorStop(0.5,"green");
grd.addColorStop(1,"blue");
context.fillStyle=grd;
context.fillRect(10,10,180,180);
Result:
Drawing on a canvas with Javascript
We’ve already looked at several methods for drawing on the canvas with javascript including paths, text, and gradients. Can we literally draw on the cavas?
Up until now we’ve been drawing or painting items to the canvas. Outside of the computer world, drawing or painting items to a canvas is referred to as art (although the term art is questionable in some cases). The intent of this article is to introduce the power of the HTML5 Canvas to the reader by developing a simple game. To achieve this we need to allow our art to be interactive. That is to say that user input needs to impact what is drawn on the screen.
So, can we literally draw on the canvas with user input? Yes, with a little help from an event handler we can accept mouse input from the user.
canvas.addEventListener("mousemove", onMouseMove, false);
Using the mouse handler code snippet, we can create a very simple HTML5 canvas paint program where the user can draw on the screen using the mouse.
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="600" height="300" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
<script>
function onMouseMove(event)
{
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var boundingRect = c.getBoundingClientRect();
var x = event.clientX - boundingRect.left;
var y = event.clientY - boundingRect.top;
ctx.fillRect(x,y,5,5);
}
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
context.fillStyle = "#000000";
canvas.addEventListener("mousemove", onMouseMove, false);
</script>
</body>
</html>
I will leave it as an exercise for the reader, but we can easily see how we can extend the power of the canvas draw program to other features. For example, mouse click to change the brush color, or maybe insert path elements, etc.
Canvas examples
Now that we know how to draw various elements on the canvas, and we know how to use event handlers to interact with user input and change the appearance of graphics on the screen we can begin to work on the game.
I keep a template handy as the starting point for my games. For those of you who have worked on an XNA game before, this template should look somewhat familiar. In the sense that there is a main game loop that has elements for drawing and updating game content.
<!DOCTYPE HTML>
<html>
<head>
<style>
body {
margin: 0px;
padding 0px;
}
#myCanvas {
border: 1px solid #9c9898;
}
</style>
<script>
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
Global = {};
function initialize(){
}
function draw(context){
}
function update() {
}
function animate() {
var canvas = document.getElementById('myCanvas');
var contex = canvas.getContext('2d');
context.clearRect(0,0,canvas.width, canvas.height);
for(i=0; i<Global.{whatever}.length-1; i++){
update(Global.{whatever}[i]);
}
requestAnimFrame(function() {
animate();
});
}
window.onload = function() {
initialize();
animate();
}
</script>
</head>
<body>
<canvas id="myCanvas" width="800" height="600"></canvas>
</body>
</html>
One of the key differences between this approach and XNA is that XNA handles refresh timing through the game timer. For javascript the frame rate is handled through a callback function in this little piece of code:
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
The key line in the above snippet of code is window.setTimeout(callback, 1000 / 60); this basically says take 1000 milliseconds, or 1 second and divide it into 60 pieces and perform our callback each time. This gives the frame rate of 60 screen refreshes per second.
Now that we know the basic layout for the game code, and how the game refreshes the screen, let’s get to work. Since I lack originality, and I don’t have a graphic artist on staff I will be creating a game called “Block Invaders”. You know the one, blocks of evil invaders from outer space fly down in a perfect rectangular array pattern to attack. While this happening, we protect earth by hiding behind floating shields and fire back.
Since the focus of this article is the HTML5 canvas, we won’t do an in depth review of the game logic. We will however call out areas of the game code where we used canvas methods that were discussed earlier in the article.
As you can imagine, every game element has a related function that renders it to the screen. These functions are called from the main animation game loop. For example the function that draws the bad guys or aliens:
function drawBadGuy(alien, context){
context.beginPath();
context.rect(alien.x, alien.y, alien.width, alien.height);
context.fillStyle = alien.fillStyle;
context.fill();
context.lineWidth = alien.borderthickness;
context.strokeStyle = 'black';
context.stroke();
}
The main animation loop loops through all the elements of the array that stores the attacking aliens and calls the drawBadGuy method to render each alien to the screen. Using the canvas rect, fillStyle, and lineWidth methods, we are easily able to create the green block invaders.
The bullets fired by the base or by the block invaders are created using the canvas arc method.
function drawBullet(b, context) {
context.beginPath();
context.arc(b.x,b.y,5,0,2*Math.PI);
context.fillStyle = 'yellow';
context.fill();
context.stroke();
}
The barriers or shields that protect the player as they shoot at the invaders are created using an array of canvas rectangles. This was done so the shields may be gradually shot away during the game. We use a radial gradient method with addColorStop to represent partial damage done to the shield by a bullet.
function drawBarrier(b, context) {
if(b.impactX == 0) {
context.beginPath();
context.rect(b.x, b.y, b.width, b.height);
context.fillStyle = 'blue';
context.fill();
}
else {
var grd=context.createRadialGradient(b.impactX, b.impactY,5,b.impactX,b.impactY,10);
grd.addColorStop(0,"white");
grd.addColorStop(1,"blue");
context.fillStyle = grd;
context.fillRect(b.x, b.y, b.width, b.height);
}
}
The last canvas method we use in the game is the Text method. The text method is used in a couple of places. The first is to provide the player feedback by displaying their score using blue stroke text in the upper left corner of the canvas.
context.font = "normal 24px Arial";
context.strokeStyle = "#0000FF";
context.strokeText("Score: " + Global.Score, 20, 40);
The second place text is used is to notify the user that the game is over. This is done in a larger 72 point font using the red stroke text canvas method. Signifying that the player has failed planet earth and the block aliens have successfully invaded.
context.font = "normal 72px Arial";
context.strokeStyle = "#FF0000";
context.strokeText("GAME", 375, 300);
context.strokeText("OVER", 375, 400);
The Block Invaders game demonstrates the power and ease with which a game may be created using the HTML5 Canvas. Around 400 lines of code and we are able to create a playable, not fully polished, but a playable game nonetheless.
Just imagine all the quarters we would have saved had this technology existed in the early 80’s.
To play the game simply download the BlockInvaders.html file and open it in your favorite HTML5 compatible browser.