How To Make An HTML5 Canvas Game

HTML5 introduced the canvas element. The canvas element presents us with a blank canvas. We can use this canvas to draw and paint anything we want. JavaScript provides us with the tools we need to dynamically animate and draw to the canvas. It not only provides drawing and an animation system, it also handles user interactions. In this tutorial we will be making a basic HTML5 Canvas framework using pure JavaScript, that can be used to make a real game. A very simple game is created at the end of the tutorial to demonstrate the strengths of the HTML5 Canvas in combination with JavaScript.

How To Make An HTML5 Canvas Game

How To Make An HTML5 Canvas Game

Click here to go directly to the end of this article and play the game.

HTML5 Canvas Basic Game Framework

Let’s create a basic game framework around the canvas element. We need an HTML5 file and a JavaScript file. The HTML5 file should include a canvas element and a reference to the JavaScript file. The JavaScript file contains our code to draw stuff to the canvas element.

Here is the HTML5 file index.html:

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>HTML5 Canvas Basic Game Framework</title>
<script type="text/javascript" src="game.js"></script>
</head>
<body>
<canvas id="viewport" width="640" height="480"></canvas>
</body>
</html>

As you can see, the JavaScript file game.js is included in the head of the html file. A canvas element is defined with the name “viewport” that has a width of 640 pixels and a height of 480 pixels. In our game.js we need to find the canvas element using its name, so we can draw on it. The framework we are creating should support a rendering loop and player interactions with the mouse. For the rendering loop, we will be using Window.requestAnimationFrame(). Mouse interactions are enabled by adding mouse event listeners.

Here is the JavaScript file game.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
// The function gets called when the window is fully loaded
window.onload = function() {
    // Get the canvas and context
    var canvas = document.getElementById("viewport"); 
    var context = canvas.getContext("2d");
 
    // Timing and frames per second
    var lastframe = 0;
    var fpstime = 0;
    var framecount = 0;
    var fps = 0;
 
    // Initialize the game
    function init() {
        // Add mouse events
        canvas.addEventListener("mousemove", onMouseMove);
        canvas.addEventListener("mousedown", onMouseDown);
        canvas.addEventListener("mouseup", onMouseUp);
        canvas.addEventListener("mouseout", onMouseOut);
 
        // Enter main loop
        main(0);
    }
 
    // Main loop
    function main(tframe) {
        // Request animation frames
        window.requestAnimationFrame(main);
 
        // Update and render the game
        update(tframe);
        render();
    }
 
    // Update the game state
    function update(tframe) {
        var dt = (tframe - lastframe) / 1000;
        lastframe = tframe;
 
        // Update the fps counter
        updateFps(dt);
    }
 
    function updateFps(dt) {
        if (fpstime > 0.25) {
            // Calculate fps
            fps = Math.round(framecount / fpstime);
 
            // Reset time and framecount
            fpstime = 0;
            framecount = 0;
        }
 
        // Increase time and framecount
        fpstime += dt;
        framecount++;
    }
 
    // Render the game
    function render() {
        // Draw the frame
        drawFrame();
    }
 
    // Draw a frame with a border
    function drawFrame() {
        // Draw background and a border
        context.fillStyle = "#d0d0d0";
        context.fillRect(0, 0, canvas.width, canvas.height);
        context.fillStyle = "#e8eaec";
        context.fillRect(1, 1, canvas.width-2, canvas.height-2);
 
        // Draw header
        context.fillStyle = "#303030";
        context.fillRect(0, 0, canvas.width, 65);
 
        // Draw title
        context.fillStyle = "#ffffff";
        context.font = "24px Verdana";
        context.fillText("HTML5 Canvas Basic Framework - Rembound.com", 10, 30);
 
        // Display fps
        context.fillStyle = "#ffffff";
        context.font = "12px Verdana";
        context.fillText("Fps: " + fps, 13, 50);
    }
 
    // Mouse event handlers
    function onMouseMove(e) {}
    function onMouseDown(e) {}
    function onMouseUp(e) {}
    function onMouseOut(e) {}
 
    // Get the mouse position
    function getMousePos(canvas, e) {
        var rect = canvas.getBoundingClientRect();
        return {
            x: Math.round((e.clientX - rect.left)/(rect.right - rect.left)*canvas.width),
            y: Math.round((e.clientY - rect.top)/(rect.bottom - rect.top)*canvas.height)
        };
    }
 
    // Call init to start the game
    init();
};

The code above draws a simple frame with a border, a title and a frames per second counter. This is what the code generates:

HTML5 Canvas Basic Game Framework

HTML5 Canvas Basic Game Framework

A Game With A Bouncing Square

Now that we have a framework, let’s create a simple game with it. We will be creating a game that has a bouncing square on the screen. When the player clicks on it, the score on the square increases, the direction in which the square is traveling changes and the speed of the square increases.

First, we define some objects and properties. The level defines the area in which the square can bounce around. The square itself has a position, dimensions and movement properties. Finally, there is a score.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    // Level properties
    var level = {
        x: 1,
        y: 65,
        width: canvas.width - 2,
        height: canvas.height - 66
    };
 
    // Square
    var square = {
        x: 0,
        y: 0,
        width: 0,
        height: 0,
        xdir: 0,
        ydir: 0,
        speed: 0
    }
 
    // Score
    var score = 0;

We need to initialize the objects and properties in the init() function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    // Initialize the game
    function init() {
        // Add mouse events
        canvas.addEventListener("mousemove", onMouseMove);
        canvas.addEventListener("mousedown", onMouseDown);
        canvas.addEventListener("mouseup", onMouseUp);
        canvas.addEventListener("mouseout", onMouseOut);
 
        // Initialize the square
        square.width = 100;
        square.height = 100;
        square.x = level.x + (level.width - square.width) / 2;
        square.y = level.y + (level.height - square.height) / 2;
        square.xdir = 1;
        square.ydir = 1;
        square.speed = 200;
 
        // Initialize the score
        score = 0;
 
        // Enter main loop
        main(0);
    }

The objects need to be updated, so let’s modify the update() function. The square needs to be moved and collisions with the edges of the level should be detected and resolved.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    // Update the game state
    function update(tframe) {
        var dt = (tframe - lastframe) / 1000;
        lastframe = tframe;
 
        // Update the fps counter
        updateFps(dt);
 
        // Move the square, time-based
        square.x += dt * square.speed * square.xdir;
        square.y += dt * square.speed * square.ydir;
 
        // Handle left and right collisions with the level
        if (square.x <= level.x) {
            // Left edge
            square.xdir = 1;
            square.x = level.x;
        } else if (square.x + square.width >= level.x + level.width) {
            // Right edge
            square.xdir = -1;
            square.x = level.x + level.width - square.width;
        }
 
        // Handle top and bottom collisions with the level
        if (square.y <= level.y) {
            // Top edge
            square.ydir = 1;
            square.y = level.y;
        } else if (square.y + square.height >= level.y + level.height) {
            // Bottom edge
            square.ydir = -1;
            square.y = level.y + level.height - square.height;
        }
    }

We need to draw the square and the score. This needs to be done in the render() function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    // Render the game
    function render() {
        // Draw the frame
        drawFrame();
 
        // Draw the square
        context.fillStyle = "#ff8080";
        context.fillRect(square.x, square.y, square.width, square.height);
 
        // Draw score inside the square
        context.fillStyle = "#ffffff";
        context.font = "38px Verdana";
        var textdim = context.measureText(score);
        context.fillText(score, square.x+(square.width-textdim.width)/2, square.y+65);
    }

The title of the demo should be modified in the drawFrame() function. The final step is to add mouse interactions. Let’s add our code to the onMouseDown() function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    function onMouseDown(e) {
        // Get the mouse position
        var pos = getMousePos(canvas, e);
 
        // Check if we clicked the square
        if (pos.x >= square.x && pos.x < square.x + square.width &&
            pos.y >= square.y && pos.y < square.y + square.height) {
 
            // Increase the score
            score += 1;
 
            // Increase the speed of the square by 10 percent
            square.speed *= 1.1;
 
            // Give the square a random position
            square.x = Math.floor(Math.random()*(level.x+level.width-square.width));
            square.y = Math.floor(Math.random()*(level.y+level.height-square.height));
 
            // Give the square a random direction
            square.xdir = Math.floor(Math.random() * 2) * 2 - 1;
            square.ydir = Math.floor(Math.random() * 2) * 2 - 1;
        }
    }

Bouncing Square Example

Here is the resulting game, made with the basic framework and some modifications. Click on the square to increase your score and to advance to the next square. The full source code is available on GitHub.