Canvas Tutorial

The box to the right is a canvas element. It is declared in HTML like this:

<canvas id="canvas" width="350" height="300"></canvas>

That's it! Now the rest is in JavaScript.

To get started, you may want to skim through this very nice introduction to Canvas. If at any time you feel a little lost, check out the tutorials at html5canvastutorials.com.

1. Drawing a Circle and a Rectangle

In order to start using the drawing APIs that allow us to draw on the canvas, first we need to get a reference to the canvas context.

var ctx = document.getElementById("canvas").getContext("2d");

Once we have that ctx reference, we can call functions on it to draw a circle. Try pasting the above and below code into your JavaScript box and running it.

ctx.beginPath();
ctx.arc(75, 75, 10, 0, Math.PI*2, true); 
ctx.closePath();
ctx.fill();

You should now see a small black circle in the canvas element to the right.

Try commenting out the arc() call and play around with ctx.rect(x, y, width, height) instead.

Also try commenting out the fill() call and substitute stroke() instead.

2. Coloring the Circle and Rectangle

If no color is specified, canvas drawings will render as black by default. To specify fill color, stroke width, and stroke color, use code like the following:

ctx.fillStyle = "#8ED6FF";
ctx.fill();
ctx.lineWidth = 5;
ctx.strokeStyle = "#0000ff";
ctx.stroke();

Try playing around with the colors and line width. Also try creating additional shapes.

3. Reusability

If you've started to create many shapes, you can see that the code quickly gets unwieldy. Let's create some simple functions to help us draw circles and rectangles more easily.

Write a function called circle that you can call like this:

circle(x, y, radius, [fillColor], [strokeWidth], [strokeColor]);

Make sure the last three arguments (in brackets) are optional, so you can call the function like circle(75,75,10) or circle(75,75,10,"#0000FF") or circle(100,200,40,"#0000FF",2,"#7777FF").

Remember that you can use the typeof operator to check if variables are undefined, like this:

if (typeof fillColor == "undefined") {
    fillColor = "#000000";
}

When you're done with the circle() function, write a similar one for creating rectangles:

rect(x, y, width, height, [fillColor], [strokeWidth], [strokeColor]);

4. Animation

Before we go any further, let's make sure your code is at least being called when the document is ready rather than immediately as it's loaded. Your code at this point should look something like this (or possibly better if you made the wise decision to namespace your code in an object or use Module pattern):

var ctx;
    
function init() {
    ctx = $('#canvas')[0].getContext("2d");
    
    // calls to the circle() function and rect() function here
}
    
function circle(x, y, radius, fillColor, strokeWidth, strokeColor) {
    // your code here
}
    
function rect(x, y, width, height, fillColor, strokeWidth, strokeColor) {
    // your code here
}
    
$(document).ready(init);

Now that we have our circle() function, it's going to make animation much easier. Here's the basic code for doing animation:

var WIDTH = 350,
    HEIGHT = 300,
    intervalId = null,
    x = 50,
    y = 50,
    dx = 1,
    dy = 2;
      
function init() {
    // your existing code here
      
    intervalId = setInterval(draw, 10);
}
      
function draw() {
    ctx.clearRect(0, 0, WIDTH, HEIGHT);
    circle(x, y, 30);
    x += dx;
    y += dy;  
}

Our call to the setInterval() function tells the browser to call the draw() function every ten milliseconds. Every time the draw() function is called, it clears the canvas, draws a circle at the location determined by the x and y variables, and then increments the x and y variables by dx and dy, respectively, so that next time the draw() function is called, the x and y variables denote a new position for the circle. So in this way, the draw() function draws out every single frame of animation, one as a time, every ten milliseconds.

Try playing around with the initial values of the x and y variables to change the initial position of the circle. Also try modifying the values of the dx and dy variables to change the rate and direction of the animation. Don't forget to try negative values for dx and dy.

5. Collision Detection

So far, our ball starts at a certain point and then it takes off in one direction. But it keeps going beyond the box, never to be seen again. Let's try some code to detect when it hits a wall so we can make it bounce off the wall.

Try the following code right after you draw the circle in the draw() function.

if (x + dx > WIDTH || x + dx < 0)
    dx = -dx;
if (y + dy > HEIGHT || y + dy < 0)
    dy = -dy;

You'll see that this code is imperfect, because the ball will go a little bit into the wall before it bounces off. Why is this? Try to fix the code so that the ball bounces as soon as its edge hits the wall.

6. Stopping the Animation

Remember how we used the setInterval() function to tell the browser to draw a frame every ten milliseconds? And remember how we assigned the return value of setInterval() to the intervalId variable? We did that to keep a reference to the ongoing interval, so we can stop it whenever we want by calling clearInterval(). Paste the below function into your code:

function stop() {
    clearInterval(intervalId);
}

Now, in the init() function, paste in the following code:

setTimeout(stop, 1000);

This call to the setTimeout() function tells the browser to call our stop() function after 1000 milliseconds, or one second.

Once you've played around with that a bit, remove the call to setTimeout() for now; we'll use stop() in a different way later on.

7. Drawing the Paddle

Now that we're not calling the stop() function anymore, what we have so far is a ball bouncing around ad infinitum. The next step is to remove the bottom as a bouncing surface and add a paddle off of which the ball can bounce.

Go ahead and modify your collision detection code to prevent the ball from bouncing off the bottom edge of the canvas.

Great! Now let's draw a paddle. Use the following variables and the rect() function to draw a rectangle representing the paddle along the bottom edge:

var paddleh = 10,
    paddlew = 75,
    paddlex = (WIDTH / 2) - (paddlew / 2);

Keeping in mind the condition you removed to open up the bottom edge, let's make it so the ball bounces off the paddle. To get you started, here's the pseudocode for the simplest solution:

// if the ball is about to cross the bottom edge:
//   - if the ball is between the left and right edge of the paddle:
//       - reverse the vertical velocity.
//   - otherwise:
//      - stop the animation.

If you haven't already, you'll probably want to reduce the ball's radius from 30 to 10 to make it a more manageable size relative to the canvas size and paddle.

8. Capturing Keyboard Events

In order to have the left and right arrow keys control the movement of the paddle, we have to detect when those two keys are being pressed and animate the paddle in the appropriate direction during the key press.

Here's some basic code to get you started:

var paddledx = 5,
    leftDown = false,
    rightDown = false;
      
function onKeyDown(e) {
      
    // if the left arrow key is being pressed, set the flag
    if (e.keyCode == 37) leftDown = true;
      
    // if the right arrow key is being pressed, set the flag
    if (e.keyCode == 39) rightDown = true;
}
      
function onKeyUp(e) {
    
    // if the left arrow key is done being pressed, unset the flag
    if (e.keyCode == 37) leftDown = false;
      
    // if the right arrow key is done being pressed, unset the flag
    if (e.keyCode == 39) rightDown = false;
}

// put these down by your $(document).ready() call
$(document).keydown(onKeyDown);
$(document).keyup(onKeyUp);

Now in your draw() function, right before you draw the rectangle representing the paddle, use the following pseudocode to change the x position of the paddle:

// if the left key is down:
//   - move the paddle to the left by the amount specified by paddledx
// if the right key is down:
//   - move the paddle to the right by the amount specified by paddledx

And that's it! Now you can move the paddle by pressing the left and right arrow keys.

9. Capturing Mouse Movement

In a very similar way to how we captured the keyboard events, we can use the onMouseMove event to tell us when the mouse has moved and where it is. Here's the starting code for capturing mouse movement:

var canvasMinX,
    canvasMaxX;
      
function init() {
      
    // your other init() code here
      
    canvasMinX = $("#canvas").offset().left;
    canvasMaxX = canvasMinX + WIDTH;
}
      
function onMouseMove(e) {
      
  // if the mouse pointer is within the x boundaries of the canvas
  if ((e.pageX > canvasMinX) && (e.pageX < canvasMaxX))
      paddlex = e.pageX - canvasMinX - (paddlew / 2);
}
      
// put this down by your $(document).ready() call
$(document).mousemove(onMouseMove);

10. Brick-Building

Now it's time to build the bricks that the ball will break while you play the game. There are three problems to solve here: (1) drawing each brick at the correct position, (2) keeping track of which bricks have been broken, and (3) how to tell when the ball hits a brick.

First problem: drawing the bricks.

var NROWS = 5,
    NCOLS = 5,
    BRICKWIDTH = ((WIDTH - 1) / NCOLS) - 1,
    BRICKHEIGHT = 15,
    PADDING = 1;
      
function draw() {
      
    // your other draw() code here
      
    for (i = 0; i < NROWS; i++) {
        for (j = 0; j < NCOLS; j++) {
      
            // call your rect() function here
      
            // try to figure out how to calculate the x and y
            // position based on loop variables i and j
      
            // the brick width and height will stay the same
        }
    }
}

Second problem: keeping track of the bricks.

Each brick will need a on/off flag so we can keep track of whether it's been broken or not. Each flag starts out as "on". When the ball hits a brick, we toggle that flag to "off".

How do we store a flag for each brick? We have five rows and five columns, which makes 25 bricks. We could make 25 separate global variables, but what if we wanted to change the number of rows or columns down the road?

We need to store them in a list of which we can easily change the length. In JavaScript, that means we use an array. But since there are both rows and columns, we'll need an array of arrays, or a two-dimensional array.

The code below creates an array of five rows. Each row contains an array of five columns. Each column is represented by the number 1. When the brick has been broken, we can set the number to 0.

var bricks = new Array(NROWS);
  
function init() {

    // your other init() code here
      
    // loop for the number of rows specified
    for (i = 0; i < NROWS; i++) {
      
        // add a row to the bricks array
        bricks[i] = new Array(NCOLS);
      
        // loop for the number of columns specified
        for (j = 0; j < NCOLS; j++) {
      
            // add a brick to the current row
            bricks[i][j] = 1;
        }
    }
}

Third problem: telling when the ball hits a brick.

I'll leave the logic to you, but here's a hint:

function draw() {
    
    // your other draw() code here
    
    // calculate the row and column of bricks that the ball is in
    row = Math.floor(y / (BRICKHEIGHT + PADDING));
    col = Math.floor(x / (BRICKWIDTH + PADDING));
    
    // if the ball has hit a brick
    if ([yourConditionHere]) {
        dy = -dy;
        bricks[row][col] = 0;
    }
    
    // this is your loop from Problem One that draws the bricks
    for (i = 0; i < NROWS; i++) {
        for (j = 0; j < NCOLS; j++) {
    
            // add this if condition around your rect() call 
            // so that we only draw unbroken bricks
            if (bricks[i][j] == 1) {
                // draw the brick
            }
        }
    }
}

You just completed the basic game mechanics of Breakout!

11. Make it Pretty

Here's an easy way to change the background color to black. In your draw() function, right after you call ctx.clearRect(0, 0, WIDTH, HEIGHT), do the following:

ctx.fillStyle = "#000000";
rect(0, 0, WIDTH, HEIGHT);

If you want each row of bricks to be a different color, try something like this:

var rowColors = ["#FF1C0A", "#FFFD0A", "#00A308", "#0008DB", "#EB0093"];

Here are some other enhancements to consider: