HTML5 Canvas Game

Introduction

 
The <canvas> element is one of the most popular elements of HTML5. It is widely supported by popular browsers like Firefox, Chrome, Safari, and Opera (Internet Explorer supports it in their IE9 beta version).
 
This article describes the use of the HTML5′s <canvas> element through a fun example of bouncing multiple balls.
 

What is Canvas?

 
The <canvas> element is used to draw graphics on a web page by scripting.
 
The <canvas> element is only a container for graphics. You must use a script to actually draw the graphics. Canvas has several methods for drawing paths, boxes, circles, characters, and adding images.
 
The <canvas> element has only two attributes, width, and height. These are both optional and can also be set using DOM properties. When no width and height attributes are specified, the canvas will initially be 300 pixels wide and 150 pixels high. The element can be sized arbitrarily by CSS, but during rendering, the image is scaled to fit its layout size.
 
The <canvas> element can be styled just like any normal image (margin, border, background, etc).
 

Canvas Element

 
Let's add our <canvas> element inside the <body> tag. Though we only have one <canvas> element in our HTML for this example, I have still assigned an ID to it (myCanvas) just to make it easier to select it later on via JavaScript. I also defined the element's dimensions (width and height), however, you could just as well do that via CSS by targeting the #myCanvas ID.
 

JavaScript

 
Let's begin with our actual work of creating our shapes in JavaScript.
 

Drawing Balls

 
We will draw the balls using the arc( ) and fill( ) methods. Basically, we define the context, initiate the drawing, then we use color and style methods to fill in the color and dictate the shape (using a Math object for the ball).
 
The script code has a set of variables and  functions: initBalls( ), getMousePos( ), updateBalls( ), Ball( ) and animate( ). 
 

getMousePos( ) method

 
It gets the position of the Canvas. It then gets the relative mouse position.
  1. function getMousePos(canvas, evt)  
  2. {  
  3.             // get canvas position  
  4.             var obj = canvas;  
  5.             var top = 0;  
  6.             var left = 0;  
  7.             while (obj.tagName != 'BODY')  
  8.             {  
  9.                 top += obj.offsetTop;  
  10.                 left += obj.offsetLeft;  
  11.                 objobj = obj.offsetParent;  
  12.             }  
  13.            // return relative mouse position  
  14.             var mouseX = evt.clientX - left + window.pageXOffset;  
  15.             var mouseY = evt.clientY - top + window.pageYOffset;  
  16.             return {  
  17.                 x: mouseX,  
  18.                 y: mouseY  
  19.             };  
  20. }  
  21. updateBalls( ) method  
  22. In this method, we will set the following features.  
  23. Set the position of the balls based on velocity  
  24. ball.y += ball.vy;  
  25. ball.x += ball.vx;  
  26. Setting the Restore Force  
  27. if (ball.x > ball.origX)  
  28. {  
  29.     ball.vx -restoreForce;  
  30. }  
  31. else  
  32. {  
  33.      ball.vx += restoreForce;  
  34. }  
  35. if (ball.y > ball.origY)  
  36. {  
  37.     ball.vy -restoreForce;  
  38. }  
  39. else  
  40. {  
  41.     ball.vy += restoreForce;  
  42. }  
  43. Setting the mouse force  
  44.    var mouseX = mousePos.x;  
  45.    var mouseY = mousePos.y;  
  46.    
  47.    var distX = ball.x - mouseX;  
  48.    var distY = ball.y - mouseY;  
  49.    
  50.    var radius = Math.sqrt(Math.pow(distX, 2) + Math.pow(distY, 2));  
  51.    
  52.    var totalDist = Math.abs(distX) + Math.abs(distY);  
  53.    
  54.    var forceX = (Math.abs(distX) / totalDist) * (1 / radius) * mouseForceMultiplier;  
  55.    var forceY = (Math.abs(distY) / totalDist) * (1 / radius) * mouseForceMultiplier;  
  56. Setting the mouse on the left of the Ball  
  57. if (distX > 0)  
  58. {  
  59.     ball.vx += forceX;  
  60. }  
  61. else  
  62. {  
  63.     ball.vx -forceX;  
  64. }  
  65. Setting the mouse on the Right of the ball  
  66.    
  67. if (distY > 0)  
  68. {  
  69.     ball.vy += forceY;  
  70. }  
  71. else  
  72. {  
  73.      ball.vy -forceY;  
  74. }  
  75.    
  76. Setting the floor friction  
  77.    
  78. if (ball.vx > 0)  
  79. {  
  80.     ball.vx -floorFriction;  
  81. }  
  82. else if (ball.vx < 0)  
  83. {  
  84.      ball.vx += floorFriction;  
  85. }  
  86. if (ball.vy > 0)  
  87. {  
  88.       ball.vy -floorFriction;  
  89. }  
  90. else if (ball.vy < 0)  
  91. {  
  92.        ball.vy += floorFriction;  

Limiting the area of the boundary

 
Now it's time to bounce the ball off the corners of the <canvas> element.
 
Setting up the limit for the floor condition
  1. if (ball.y > (canvas.height - ball.radius))  
  2. {  
  3.      ball.y = canvas.height - ball.radius - 2;  
  4.      ball.vy *= -1;  
  5.      ball.vy *= (1 - collisionDamper);  
  6. }  
  7.    
  8. Setting up the limit for the ceiling condition  
  9.    
  10. if (ball.y < (ball.radius))  
  11. {  
  12.       ballball.y = ball.radius + 2;  
  13.       ball.vy *= -1;  
  14.       ball.vy *= (1 - collisionDamper);  
Setting up the limit for the right wall condition
  1. if (ball.x > (canvas.width - ball.radius))  
  2. {  
  3.       ball.x = canvas.width - ball.radius - 2;  
  4.       ball.vx *= -1;  
  5.       ball.vx *= (1 - collisionDamper);  
  6. }  
  7.    
  8. Setting up the limit the left wall condition  
  9.    
  10. if (ball.x < (ball.radius))  
  11. {  
  12.        ballball.x = ball.radius + 2;  
  13.        ball.vx *= -1;  
  14.        ball.vx *= (1 - collisionDamper);  
  15. }  
  16. Creating the ball constructor  
  17. function Ball(x, y, vx, vy, color)  
  18. {  
  19.     this.x = x;  
  20.     this.y = y;  
  21.     this.vx = vx;  
  22.     this.vy = vy;  
  23.     this.color = color;  
  24.     this.origX = x;  
  25.     this.origY = y;  
  26.     this.radius = 10;  
Setting the mouse position really far away so the mouse forces are nearly obsolete
  1. var mousePos = {x: 9999, y: 9999}; 

Move the Balls

 
Now that we have the balls, let's try to move them. We'll replace the hardcoded values of the coordinates in the .arc method with variables x and y, that we will then increment by an amount of dx and dy.
  • //move balls ball.y += ball.vy; ball.x += ball.vx;  
  • //draw it context.arc(ball.x, ball.y, ball.radius, 0, 2 * Math.PI, false); context.fillStyle = ball.color; context.fill();
Example
  1. <!DOCTYPE html>  
  2.    
  3. <html lang="en" xmlns="http://www.w3.org/1999/xhtml">  
  4. <head>  
  5.     <meta charset="utf-8" />  
  6.     <title>Canvas Game</title>  
  7.     <style>  
  8.         body {  
  9.             margin: 0px;  
  10.             padding: 0px;  
  11.         }  
  12.     </style>  
  13. </head>  
  14. <body>  
  15.     <canvas id="myCanvas" width="578" height="200" style="border: 2px solid black;"></canvas>  
  16.     <script>  
  17.         window.requestAnimFrame = (function (callback) {  
  18.             return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||  
  19.             function (callback) {  
  20.                 window.setTimeout(callback, 1000 / 60);  
  21.             };  
  22.         })();  
  23.    
  24.         function initBalls() {  
  25.             balls = [];  
  26.    
  27.             var blue = '#3A5BCD';  
  28.             var red = '#EF2B36';  
  29.             var yellow = '#FFC636';  
  30.             var green = '#02A817';  
  31.    
  32.             // G  
  33.             balls.push(new Ball(173, 63, 0, 0, blue));  
  34.             balls.push(new Ball(158, 53, 0, 0, blue));  
  35.             balls.push(new Ball(143, 52, 0, 0, blue));  
  36.             balls.push(new Ball(130, 53, 0, 0, blue));  
  37.             balls.push(new Ball(117, 58, 0, 0, blue));  
  38.             balls.push(new Ball(110, 70, 0, 0, blue));  
  39.             balls.push(new Ball(102, 82, 0, 0, blue));  
  40.             balls.push(new Ball(104, 96, 0, 0, blue));  
  41.             balls.push(new Ball(105, 107, 0, 0, blue));  
  42.             balls.push(new Ball(110, 120, 0, 0, blue));  
  43.             balls.push(new Ball(124, 130, 0, 0, blue));  
  44.             balls.push(new Ball(139, 136, 0, 0, blue));  
  45.             balls.push(new Ball(152, 136, 0, 0, blue));  
  46.             balls.push(new Ball(166, 136, 0, 0, blue));  
  47.             balls.push(new Ball(174, 127, 0, 0, blue));  
  48.             balls.push(new Ball(179, 110, 0, 0, blue));  
  49.             balls.push(new Ball(166, 109, 0, 0, blue));  
  50.             balls.push(new Ball(156, 110, 0, 0, blue));  
  51.    
  52.             // O  
  53.             balls.push(new Ball(210, 81, 0, 0, red));  
  54.             balls.push(new Ball(197, 91, 0, 0, red));  
  55.             balls.push(new Ball(196, 103, 0, 0, red));  
  56.             balls.push(new Ball(200, 116, 0, 0, red));  
  57.             balls.push(new Ball(209, 127, 0, 0, red));  
  58.             balls.push(new Ball(223, 130, 0, 0, red));  
  59.             balls.push(new Ball(237, 127, 0, 0, red));  
  60.             balls.push(new Ball(244, 114, 0, 0, red));  
  61.             balls.push(new Ball(242, 98, 0, 0, red));  
  62.             balls.push(new Ball(237, 86, 0, 0, red));  
  63.             balls.push(new Ball(225, 81, 0, 0, red));  
  64.    
  65.             // O  
  66.             var oOffset = 67;  
  67.             balls.push(new Ball(oOffset + 210, 81, 0, 0, yellow));  
  68.             balls.push(new Ball(oOffset + 197, 91, 0, 0, yellow));  
  69.             balls.push(new Ball(oOffset + 196, 103, 0, 0, yellow));  
  70.             balls.push(new Ball(oOffset + 200, 116, 0, 0, yellow));  
  71.             balls.push(new Ball(oOffset + 209, 127, 0, 0, yellow));  
  72.             balls.push(new Ball(oOffset + 223, 130, 0, 0, yellow));  
  73.             balls.push(new Ball(oOffset + 237, 127, 0, 0, yellow));  
  74.             balls.push(new Ball(oOffset + 244, 114, 0, 0, yellow));  
  75.             balls.push(new Ball(oOffset + 242, 98, 0, 0, yellow));  
  76.             balls.push(new Ball(oOffset + 237, 86, 0, 0, yellow));  
  77.             balls.push(new Ball(oOffset + 225, 81, 0, 0, yellow));  
  78.    
  79.             // G  
  80.             balls.push(new Ball(370, 80, 0, 0, blue));  
  81.             balls.push(new Ball(358, 79, 0, 0, blue));  
  82.             balls.push(new Ball(346, 79, 0, 0, blue));  
  83.             balls.push(new Ball(335, 84, 0, 0, blue));  
  84.             balls.push(new Ball(330, 98, 0, 0, blue));  
  85.             balls.push(new Ball(334, 111, 0, 0, blue));  
  86.             balls.push(new Ball(348, 116, 0, 0, blue));  
  87.             balls.push(new Ball(362, 109, 0, 0, blue));  
  88.             balls.push(new Ball(362, 94, 0, 0, blue));  
  89.             balls.push(new Ball(355, 128, 0, 0, blue));  
  90.             balls.push(new Ball(340, 135, 0, 0, blue));  
  91.             balls.push(new Ball(327, 142, 0, 0, blue));  
  92.             balls.push(new Ball(325, 155, 0, 0, blue));  
  93.             balls.push(new Ball(339, 165, 0, 0, blue));  
  94.             balls.push(new Ball(352, 166, 0, 0, blue));  
  95.             balls.push(new Ball(367, 161, 0, 0, blue));  
  96.             balls.push(new Ball(371, 149, 0, 0, blue));  
  97.             balls.push(new Ball(366, 137, 0, 0, blue));  
  98.    
  99.             // L  
  100.             balls.push(new Ball(394, 49, 0, 0, green));  
  101.             balls.push(new Ball(381, 50, 0, 0, green));  
  102.             balls.push(new Ball(391, 61, 0, 0, green));  
  103.             balls.push(new Ball(390, 73, 0, 0, green));  
  104.             balls.push(new Ball(392, 89, 0, 0, green));  
  105.             balls.push(new Ball(390, 105, 0, 0, green));  
  106.             balls.push(new Ball(390, 118, 0, 0, green));  
  107.             balls.push(new Ball(388, 128, 0, 0, green));  
  108.             balls.push(new Ball(400, 128, 0, 0, green));  
  109.    
  110.             // E  
  111.             balls.push(new Ball(426, 101, 0, 0, red));  
  112.             balls.push(new Ball(436, 98, 0, 0, red));  
  113.             balls.push(new Ball(451, 95, 0, 0, red));  
  114.             balls.push(new Ball(449, 83, 0, 0, red));  
  115.             balls.push(new Ball(443, 78, 0, 0, red));  
  116.             balls.push(new Ball(430, 77, 0, 0, red));  
  117.             balls.push(new Ball(418, 82, 0, 0, red));  
  118.             balls.push(new Ball(414, 93, 0, 0, red));  
  119.             balls.push(new Ball(412, 108, 0, 0, red));  
  120.             balls.push(new Ball(420, 120, 0, 0, red));  
  121.             balls.push(new Ball(430, 127, 0, 0, red));  
  122.             balls.push(new Ball(442, 130, 0, 0, red));  
  123.             balls.push(new Ball(450, 125, 0, 0, red));  
  124.    
  125.             return balls;  
  126.         }  
  127.         function getMousePos(canvas, evt) {  
  128.    
  129.             var obj = canvas;  
  130.             var top = 0;  
  131.             var left = 0;  
  132.             while (obj.tagName != 'BODY') {  
  133.                 top += obj.offsetTop;  
  134.                 left += obj.offsetLeft;  
  135.                 objobj = obj.offsetParent;  
  136.             }  
  137.    
  138.             var mouseX = evt.clientX - left + window.pageXOffset;  
  139.             var mouseY = evt.clientY - top + window.pageYOffset;  
  140.             return {  
  141.                 x: mouseX,  
  142.                 y: mouseY  
  143.             };  
  144.         }  
  145.         function updateBalls(canvas, balls, timeDiff, mousePos) {  
  146.             var context = canvas.getContext('2d');  
  147.             var collisionDamper = 0.3;  
  148.             var floorFriction = 0.0005 * timeDiff;  
  149.             var mouseForceMultiplier = 1 * timeDiff;  
  150.             var restoreForce = 0.002 * timeDiff;  
  151.    
  152.             for (var n = 0; n < balls.length; n++) {  
  153.                 var ball = balls[n];  
  154.    
  155.                 ball.y += ball.vy;  
  156.                 ball.x += ball.vx;  
  157.    
  158.                 if (ball.x > ball.origX) {  
  159.                     ball.vx -restoreForce;  
  160.                 }  
  161.                 else {  
  162.                     ball.vx += restoreForce;  
  163.                 }  
  164.                 if (ball.y > ball.origY) {  
  165.                     ball.vy -restoreForce;  
  166.                 }  
  167.                 else {  
  168.                     ball.vy += restoreForce;  
  169.                 }  
  170.    
  171.                 var mouseX = mousePos.x;  
  172.                 var mouseY = mousePos.y;  
  173.    
  174.                 var distX = ball.x - mouseX;  
  175.                 var distY = ball.y - mouseY;  
  176.    
  177.                 var radius = Math.sqrt(Math.pow(distX, 2) + Math.pow(distY, 2));  
  178.    
  179.                 var totalDist = Math.abs(distX) + Math.abs(distY);  
  180.    
  181.                 var forceX = (Math.abs(distX) / totalDist) * (1 / radius) * mouseForceMultiplier;  
  182.                 var forceY = (Math.abs(distY) / totalDist) * (1 / radius) * mouseForceMultiplier;  
  183.    
  184.                 if (distX > 0) {  
  185.                     ball.vx += forceX;  
  186.                 }  
  187.                 else {  
  188.                     ball.vx -forceX;  
  189.                 }  
  190.                 if (distY > 0) {  
  191.                     ball.vy += forceY;  
  192.                 }  
  193.                 else {  
  194.                     ball.vy -forceY;  
  195.                 }  
  196.    
  197.                 if (ball.vx > 0) {  
  198.                     ball.vx -floorFriction;  
  199.                 }  
  200.                 else if (ball.vx < 0) {  
  201.                     ball.vx += floorFriction;  
  202.                 }  
  203.                 if (ball.vy > 0) {  
  204.                     ball.vy -floorFriction;  
  205.                 }  
  206.                 else if (ball.vy < 0) {  
  207.                     ball.vy += floorFriction;  
  208.                 }  
  209.    
  210.                 if (ball.y > (canvas.height - ball.radius)) {  
  211.                     ball.y = canvas.height - ball.radius - 2;  
  212.                     ball.vy *= -1;  
  213.                     ball.vy *= (1 - collisionDamper);  
  214.                 }  
  215.    
  216.                 if (ball.y < (ball.radius)) {  
  217.                     ballball.y = ball.radius + 2;  
  218.                     ball.vy *= -1;  
  219.                     ball.vy *= (1 - collisionDamper);  
  220.                 }  
  221.    
  222.                 if (ball.x > (canvas.width - ball.radius)) {  
  223.                     ball.x = canvas.width - ball.radius - 2;  
  224.                     ball.vx *= -1;  
  225.                     ball.vx *= (1 - collisionDamper);  
  226.                 }  
  227.    
  228.                 if (ball.x < (ball.radius)) {  
  229.                     ballball.x = ball.radius + 2;  
  230.                     ball.vx *= -1;  
  231.                     ball.vx *= (1 - collisionDamper);  
  232.                 }  
  233.             }  
  234.         }  
  235.         function Ball(x, y, vx, vy, color) {  
  236.             this.x = x;  
  237.             this.y = y;  
  238.             this.vx = vx;  
  239.             this.vy = vy;  
  240.             this.color = color;  
  241.             this.origX = x;  
  242.             this.origY = y;  
  243.             this.radius = 10;  
  244.         }  
  245.         function animate(canvas, balls, lastTime, mousePos) {  
  246.             var context = canvas.getContext('2d');  
  247.    
  248.             var date = new Date();  
  249.             var time = date.getTime();  
  250.             var timetimeDiff = time - lastTime;  
  251.             updateBalls(canvas, balls, timeDiff, mousePos);  
  252.             lastTime = time;  
  253.    
  254.             context.clearRect(0, 0, canvas.width, canvas.height);  
  255.    
  256.             for (var n = 0; n < balls.length; n++) {  
  257.                 var ball = balls[n];  
  258.                 context.beginPath();  
  259.                 context.arc(ball.x, ball.y, ball.radius, 0, 2 * Math.PI, false);  
  260.                 context.fillStyle = ball.color;  
  261.                 context.fill();  
  262.             }  
  263.             requestAnimFrame(function () {  
  264.                 animate(canvas, balls, lastTime, mousePos);  
  265.             });  
  266.         }  
  267.         var canvas = document.getElementById('myCanvas');  
  268.         var balls = initBalls();  
  269.         var date = new Date();  
  270.         var time = date.getTime();  
  271.    
  272.         var mousePos = {  
  273.             x: 9999,  
  274.             y: 9999  
  275.         };  
  276.         canvas.addEventListener('mousemove', function (evt) {  
  277.             var pos = getMousePos(canvas, evt);  
  278.             mousePos.x = pos.x;  
  279.             mousePos.y = pos.y;  
  280.         });  
  281.         canvas.addEventListener('mouseout', function (evt) {  
  282.             mousePos.x = 9999;  
  283.             mousePos.y = 9999;  
  284.         });  
  285.         animate(canvas, balls, time, mousePos);  
  286.     </script>  
  287. </body>  
  288. </html> 
Output
 
Initially, at the beginning we get
 
game1.jpg
 
On mouseHover we get
 
game2.jpg
 
Finally, at last we get
 
game3.jpg