HTML5 Canvas Tutorial: Applying Velocity, Gravity, Friction, and Elasticity

In this tutorial, you will learn to apply gravity, friction, and elasticity to a moving object which in this case will be a ball. In an additional tutorial, we will learn to add a couple of mouse events to our canvas so we can interact with the ball by dragging and tossing it around. I am assuming you have some experience with JavaScript and HTML5 Canvas. At the very minimum, you should be able to create the basic HTML layout and reference the canvas tag inside the scripting tags or an external JS file and be able to create simple animations using the Drawing API and the document.setInterval() function. I will also be using Modernizr.js to check if our end user is able to run the canvas tag. I have hosted the Modernzr.js version I am using in my Github account, here is the link
https://cdn.rawgit.com/moczka/One-Ball-Physics/master/modernizr.js
You will learn to create this simulation in this tutorial, it is small because I wanted readers on mobile devices to be able to see the simulation. Press the Start button to begin the simulation.
In order to make it easier for you guys to dive right into the core aspects of this tutorial, I will provide you with the starting HTML tag layout and JavaScript code. All you need to do is copy this code and paste it onto any text editor (I use Brackets) and save it with a (.html) file extension. Here it is:
<!DOCTYPE html> <html> <head> <title>Experimenting Canvas</title> <script src="https://cdn.rawgit.com/moczka/One-Ball-Physics/master/modernizr.js"></script> <script> //adds the event listeners to be triggered when everything has loaded window.addEventListener("load", onWindowLoad, false); function onWindowLoad(){ //calls the main function after the window event has triggerd canvasApp(); } //function part of the modernizr to check if canvas is supported. function canvasSupport () { return Modernizr.canvas; } function canvasApp(){ //creates the canvas objet and gets its context. var theCanvas = document.getElementById("theCanvas"); var context = theCanvas.getContext("2d"); //Where we will have all the code to control the canvas. function drawScreen(){ //function in charge of doing all the drawing on the canvas. } function gameLoop(){ drawScreen(); window.setInterval(gameLoop, 20); } } </script> </head> <body> <canvas id="theCanvas" width="500" height="500" style="border: 1px solid #000;"></canvas> <br> </body> </html>
Understanding Velocity

Before we get into applying gravity or even friction, we need to understand velocity. If you understand velocity and how to apply it, you can skip this part. Velocity is a vector property, meaning it has a magnitude and a direction. The magnitude of the velocity is the speed and the direction is the angle that the object is traveling at. For example, if we start our ball at the bottom left corner of our canvas and instruct it to travel at 45 degrees (PI/4) angle north and at a speed of 5 pixels per frame, we will need to calculate the X and Y components independently add the result to the ball's x and y properties on every frame to make it move along that vector. We will use the cosine and sine functions to calculate each component of the velocity vector. Remember, any trig functions of the Math class in JavaScript only accepts radians as parameters, so you will need to convert from degrees to radians. The Cosine function will give us the value for our X component by having the angle in (PI/4) as its parameter and multiplying it by the magnitude which is our speed (5 pixels). The same will be done for the Sine function which will give us the Y component value by performing the same operation.
speed = 5; angle = 45; //Convert angle from degrees to radians angle = 45 * Math.PI/180; velX = Math.cos(angle)*speed; velY = Math.sin(angle)*speed;
This will send the ball flying upwards at a 45 degree angle at a speed of 5 pixels per frame and will continue on forever unless any other force acts on the ball. Hopefully this refreshes your memory on applying velocity, this section was not meant to be an in depth look at velocity, just a refresher.
Creating Our Ball Object
For this example, we will begin by creating a variable called "ball" which will be our ball object with the following properties: x, y, radius, color, angle, speed, velX, velY.
var ball = {radius:20, x:theCanvas.width/2, y:theCanvas.height, color:"#00FF00", angle:-45, speed:5, velX:0, velY:0};
You should have this object inside the canvasApp() function scope, right below the canvas and its context declaration. We also set the X and Y property values of our ball object to half the width and half the height of our canvas so that the ball is positioned at the center of the canvas. We also have velX and velY which are the velocity components of the ball as well as speed and a string value for color. We are creating these properties now because we will use them later on when we add velocity and draw the ball.
//creates the canvas object and gets its context. var theCanvas = document.getElementById("theCanvas"); var context = theCanvas.getContext("2d"); //Creates our ball object. var ball = {radius:20, x:theCanvas.width/2, y:theCanvas.height/2, color:"#00FF00", angle:-45, speed:5, velX:0, velY:0};
Later we will position our ball on the canvas by drawing it.
Positioning Our Ball
Now that we have all the variables necessary, it is time to create our ball and draw it on the canvas. All the code to draw the ball will be inside the drawScreen() function in charged of doing all the drawing. Given the nature of Canvas, the first two lines inside this drawScreen() function, will be to clear the canvas on every frame. If you have done simple canvas animations in the past, this should not come as a surprise.
//Clears the canvas on every call. make use of alpha to create trailing effect. context.fillStyle = "rgba(255,255,255, 0.4)"; context.fillRect(0,0, theCanvas.width, theCanvas.height);
Again, for this tutorial, I am assuming you have done at least very simple animations before. Or at the very least understand the intermediate nature of canvas and how it differs from a display list system like Flash. We set our context fillStyle to a string RGB value of 255, 255, 255 which is the color white, but we also include an alpha value of 0.4 to create a trailing effect. The following lines make use of the Canvas drawing API arc() function which takes in 6 values as its parameters.
- X position value for the arc.
- Y position value for the arc.
- Radius value for the arc.
- The starting angle of your arc in (RADIANS).
- The ending angle of your arc in (RADIANS).
- A Boolean value, true for clockwise and false for counterclockwise. Drawing direction of your arc.
For our ball, we will use its properties as the parameters for this arc function. Before making use of the arc function, we have to specify a context fillStyle which we will set to be the color of our ball. We then have to begin a path, close that path and then fill the path with the color of our ball, as so:
//draws the ball context.fillStyle = ball.color; context.beginPath(); context.arc(ball.x, ball.y, ball.radius, 0, Math.PI*2, true); context.closePath(); context.fill();
So far all you will see when you run the program will be a static green ball right at the center of the canvas. Our next section will add velocity and gravity.
Adding Velocity and Gravity to The Ball
A static ball is too boring, let's add some motion by adding velocity and gravity to our ball. In order to add velocity, we need to calculate the x and y component of our velocity vector before we run the drawScreen() function. So right after the declaration of the ball object, let's give our ball a launch angle of -45 degrees and right after that line, convert the angle into radians by multiplying it by (Math.PI/180) which is the conversion factor. The next two lines will calculate the X component by using the Math.cos() function and having the launch angle as its parameter and multiplying it by the speed. The same is done to calculate the Y velocity component but using the Math.sin() function instead.
//Give our ball a launch angle in degrees. ball.angle = -45; //converts our starting angle to radians. ball.angle = ball.angle * Math.PI / 180; //calculates our starting velocity given our angle. ball.velX = Math.cos(ball.angle)*ball.speed; ball.velY = Math.sin(ball.angle)*ball.speed;
We set the angle to a negative value given the nature of the canvas coordinate plane. We also save every result into its own property of the ball object as shown by the code above. Now that we have these values, in our drawScreen() function, we will use them to dynamically change the ball's x and y position values so to create motion.
Now we need to create a variable that will hold our gravity value. This variable will not be a property of the ball object as it is a property of the "outside world." Create a variable named "gravity" and set it equal to (0.2) right below our ball object declaration.
//Give our ball a launch angle in degrees.
ball.angle = -45;
//converts our starting angle to radians.
ball.angle = ball.angle * Math.PI / 180;
//calculates our starting velocity given our angle.
ball.velX = Math.cos(ball.angle)*ball.speed;
ball.velY = Math.sin(ball.angle)*ball.speed;
//Sets our starting gravity value.
var gravity = 0.2;
In our drawScreen() function, we add gravity to our ball.velY property and then add that property to the ball.y property. The reason why we add it to our ball Y velocity component is because gravity pulls downwards towards the bottom "wall" of the canvas so we add values to the Y velocity component. This means that on every frame, gravity will be added to the y velocity component which will then be added to the ball y position causing it to gradually increase simulating a falling object. As for the x component of the velocity vector, we just add that to the ball.x property. Since no external force (as of now) is acting on the x component of the velocity vector, we leave it as is.
//Clears the canvas on every call. make use of alpha to create trailing effect. context.fillStyle = "rgba(255,255,255, 0.4)"; context.fillRect(0,0, theCanvas.width, theCanvas.height); //Gravity is added to the Y component of the ball velocity vector ball.velY += gravity; //X and Y components of the velocity vector are added to the ball x and y values. ball.x += ball.velX; ball.y += ball.velY;
If you run the program now, you will see your ball be launched at a 45 degree angle up and then begin to fall down even going beyond the canvas boundaries. In the next section, we will make the ball bounce back when it hits the boundaries.
Making The Ball Bounce Back
Before we get into the code to make the ball bounce back. There is an important concept I want you to understand. Velocity is a vector property, meaning it has a magnitude and a DIRECTION. We break down this velocity vector into its X and Y components. We use those components to change the ball's position by adding them to the ball x and y position values. If we want to make our ball bounce back, that means that we want the ball to travel in the OPPOSITE direction, meaning we have to reverse the DIRECTION of the velocity vector. In math, the way we reverse a vector is by multiplying it by -1, basically making the vector negative. The following figure shows the direction in which the ball would travel if its X and Y velocity components were either positive or negative.

So when our ball hits the right or left "wall" or canvas boundary, we need to reverse the ball velocity X component. And when the ball hits either the top or bottom "wall" we reverse the ball Y velocity component. To handle this operation, we will create an external function inside of the canvasApp() scope that we will name checkBoundary(). This function will receive the ball object as its parameter, we will have a couple of if statements comparing the ball x and y location to the total width and height of the canvas. Keep in mind that the x and y position values of the ball are right at the center, so we will need to keep in mind the radius length of the ball to accurately determine when to make the ball bounce back.
function checkBoundary(object){ //if ball has hit any of the right or left sides, reverse its X component velocity vector if(object.x>=theCanvas.width-object.radius){ object.x = theCanvas.width - object.radius; object.velX = -object.velX; }else if(object.x<=object.radius){ object.x = object.radius; object.velX = -object.velX; //if ball has hit any of the top or bottom sides, reverse its Y component velocity vector }else if(object.y>=theCanvas.height-object.radius){ object.y = theCanvas.height-object.radius; object.velY = -object.velY; }else if(object.y<=0+object.radius){ object.y = object.radius; object.velY = -object.velY; } }
Notice how we are not directly referencing the ball object properties but instead we are using the object that the function receives as its parameter. I have decided to write the function this way so that we can use it in other programs or if we want to have multiple balls bouncing around. In our drawScreen() function right before drawing the ball, we will call the checkBoundary() function and have the ball object as its parameter.
ball.x += ball.velX;
ball.y += ball.velY;
//checks if it has hit any of the wall boundaries
checkBoundary(ball);
//draws the ball
context.fillStyle = ball.color;
context.beginPath();
context.arc(ball.x, ball.y, ball.radius, 0, Math.PI*2, true);
context.closePath();
context.fill();
If you run the program, you will see the ball bounce back whenever it hits any of the boundaries and keep bouncing. However, in the real world, whenever a ball bounces it loses some of its kinetic energy on every bounce. This is called "elasticity", in the next section we will be adding some elasticity.
Adding Elasticity To The Ball
Elasticity is a property of our ball, different materials have different degrees of elasticity, so we will add an elasticity property to our ball object declaration and set it equal to a decimal value of (0.80).
var ball = {radius:20, x:0, y:0, color:"#00FF00", angle:-45, speed:5, velX:0, velY:0, elasticity:0.80};
The decimal represents the percentage of kinetic energy or "velocity" that the ball gets to "keep" on every bounce. For example, the ball is traveling at 5 pixels per frame then after the first bounce it is now traveling at (5 x 0.80 = 4) 4 pixels per frame. Second bounce it is now traveling at 3.2 pixels per frame and so on. To apply this, you simply have to multiply the X or Y component of the velocity vector by the ball elasticity, depending on which "wall" the ball has hit. If the ball hits either the right or left side wall, you multiply the X velocity component by the ball elasticity. For the top or bottom wall, we deal with the Y velocity component. These calculations go in the checkBoundary() function as it is the function that checks for the ball collisions against the canvas walls.
function checkBoundary(object){ //if ball has hit any of the right or left sides, reverse its X component velocity vector if(object.x>=theCanvas.width-object.radius){ object.x = theCanvas.width - object.radius; object.velX = -object.velX; object.velX *= object.elasticity; }else if(object.x<=object.radius){ object.x = object.radius; object.velX = -object.velX; object.velX *= object.elasticity; //if ball has hit any of the top or bottom sides, reverse its Y component velocity vector }else if(object.y>=theCanvas.height-object.radius){ object.y = theCanvas.height-object.radius; object.velY = -object.velY; object.velY *= object.elasticity; }else if(object.y<=0+object.radius){ object.y = object.radius; object.velY = -object.velY; object.velY *= object.elasticity; } }
These changes will make the ball "act" more according to real world physics. However, once the ball has lost most of its kinetic energy in the Y direction, it might continue moving along the X coordinate because no external force is acting along the X. In the real world, friction from the floor or even the air goes against a ball and acts on the X velocity component which is why a ball eventually stops moving. In the next section, we will be adding friction to our simulation.
Adding Friction To Our Ball
This is by no means a physics class, but it helps to understand core definitions of some of these physical properties to paint a clearer picture as to how they relate to our simulation. Friction is the "resistance" that a surface or object encounters when moving along another object or surface. In simple terms, how difficult it is to move an object along another. If friction is greater than the force being applied along the object, the object will not move. Friction is usually very small but it all depends on the surface or object. For our example, we will have friction equal to a very small amount of (0.1). In our example, friction will be affecting our ball velocity vector. However, there is a very important key point to keep in mind when applying friction. The amount of friction we take away from our velocity vector has to be proportional to the velocity. In other words, we have to multiply friction by the current velocity value and then use that product to subtract away from the velocity. If we just take away our friction value (0.1) from velocity, it will reach a point where the numbers will start going into negative values causing the ball to travel in the opposite direction. For that reason, the amount subtracted from the velocity has to be proportional.
Now that that key concept is clear, it is time to create a variable that will hold our friction value. Remember, this variable is not a property of the ball so it will go below our gravity value. We then use this variable in the drawScreen() function to alter our ball X velocity component.
//Gravity is added to the Y component of the ball velocity vector
ball.velY += gravity;
//Friction is proportionally substracted from the X component of the velocity vector.
ball.velX = ball.velX - (ball.velX*friction);
//X and Y components of the velocity vector are added to the ball x and y values.
ball.x += ball.velX;
ball.y += ball.velY;
This will cause our ball to eventually stop moving along the X axis. Notice how we are not applying this friction value to the Y velocity component and that is because gravity will eventually cause the ball to stop moving in the Y direction.
Optional: Adding an ON/OFF Switch To The Animation
For debugging purposes or added functionality, I like to have a "pause" button below the canvas so I can stop the simulation at any time and either take screen shots for my tutorials or to check the console outputs to fix any bug. The first thing is to create a variable called gameOn that will hold a Boolean value of true. We then enclose the window.setTimeout() and drawScreen function inside an if statement and have the gameOn variable as its parameter so that it is only called when the variable is set to true.
function gameLoop(){ if(gameOn){ window.setTimeout(gameLoop, 20); drawScreen(); } }
We now have to create an HTML button below our canvas. Create this button using the <input> tag and set its type attribute to "button" and id to "pauseButton"
<canvas id="theCanvas" width="500" height="500" style="border: 1px solid #000;"></canvas> <br> <input type="button" id="pauseButton" value="Pause"/>
Inside the canvasApp() function scope, after declaring the canvas object and its context, create a variable named "menuForm" that will hold our HTML input tag so that we can add an event listener. We will also create an event handler function named onPause.
//creates the canvas objet and gets its context. var theCanvas = document.getElementById("theCanvas"); var context = theCanvas.getContext("2d"); //more code here... menuForm = document.getElementById("pauseButton"); menuForm.addEventListener("click", onPause, false);
Inside our onPause event handler function, we will create set our gameOn variable equal to its opposite value. So if the variable is to true, then handler will change it to false. We will make use of the !variable operator. We then call our gameLoop() function.
//pause button handler, switching the game on and off. function onPause(e){ //sets it to its opposite value. gameOn = !gameOn; gameLoop(); }
And that's it! Now you have an on and off button to stop the simulation at any time. I will also have a tutorial on added functionality such as being able to drag and toss the ball and increase the gravity using the new HTML5 "range" attribute.
Here is the complete source code for anyone having problems. Leave feedback in the comments or if you need any help.
<!DOCTYPE html> <html> <head> <title>Experimenting Canvas</title> <script src="https://cdn.rawgit.com/moczka/One-Ball-Physics/master/modernizr.js"></script> <script> //adds the event listeners to be triggered when everything has loaded window.addEventListener("load", onWindowLoad, false); function onWindowLoad(){ //calls the main function after the window event has triggerd canvasApp(); } //function part of the modernizr to check if canvas is supported. function canvasSupport () { return Modernizr.canvas; } function canvasApp(){ //if the opposite of canvas support is true, means there is no support and exits the program. if(!canvasSupport()){ return; } //creates the canvas objet and gets its context. var theCanvas = document.getElementById("theCanvas"); var context = theCanvas.getContext("2d"); //Creates our ball object. var ball = {radius:20, x:theCanvas.width/2, y:theCanvas.height/2, color:"#00FF00", angle:0, speed:10, velX:0, velY:0, elasticity:0.80}; ball.angle = -45; //converts our starting angle to radians. ball.angle = ball.angle * Math.PI / 180; //calculates our starting velocity given our angle. ball.velX = Math.cos(ball.angle)*ball.speed; ball.velY = Math.sin(ball.angle)*ball.speed; //Sets our starting gravity and friction value. var gravity = 0.2; var friction = 0.01; //set our gameOn variable to true so the game can start. var gameOn = true; //adds event listener for our pause and gravity range. menuForm = document.getElementById("pauseButton"); menuForm.addEventListener("click", onPause, false); //begins the game loop. gameLoop(); //function in charge of doing all the drawing. function drawScreen(){ //Clears the canvas on every call. make use of alpha to create trailing effect. context.fillStyle = "rgba(255,255,255, 0.4)"; context.fillRect(0,0, theCanvas.width, theCanvas.height); //Gravity is added to the Y component of the ball velocity vector ball.velY += gravity; //Friction is proportionally substracted from the X component of the velocity vector. ball.velX = ball.velX - (ball.velX*friction); //X and Y components of the velocity vector are added to the ball x and y values. ball.x += ball.velX; ball.y += ball.velY; //checks if it has hit any of the wall boundaries checkBoundary(ball); //draws the ball context.fillStyle = ball.color; context.beginPath(); context.arc(ball.x, ball.y, ball.radius, 0, Math.PI*2, true); context.closePath(); context.fill(); } //function in charge of checking the ball against walls. function checkBoundary(object){ //if ball has hit any of the right or left sides, reverse its X component velocity vector //Reduce its X velocity component by the ball's elasticity property if(object.x>=theCanvas.width-object.radius){ object.x = theCanvas.width - object.radius; object.velX = -object.velX; object.velX *= object.elasticity; }else if(object.x<=object.radius){ object.x = object.radius; object.velX = -object.velX; object.velX *= object.elasticity; //if ball has hit any of the top or bottom sides, reverse its Y component velocity vector //Reduce its Y velocity component by the ball's elasticity property }else if(object.y>=theCanvas.height-object.radius){ object.y = theCanvas.height-object.radius; object.velY = -object.velY; object.velY *= object.elasticity; }else if(object.y<=0+object.radius){ object.y = object.radius; object.velY = -object.velY; object.velY *= object.elasticity; } } function gameLoop(){ if(gameOn){ window.setTimeout(gameLoop, 20); drawScreen(); } } //pause button handler, switching the game on and off. function onPause(e){ //sets it to its opposite value. gameOn = !gameOn; gameLoop(); } } </script> </head> <body> <canvas id="theCanvas" width="500" height="500" style="border: 1px solid #000;"></canvas> <br> <input type="button" id="pauseButton" value="Pause"/> </body> </html>