Drawing and Animation

After finishing the previous section of this tutorial, you should have a static web page displaying a title, some text, and an image; the source code should look something like this:

<!DOCTYPE html>

<html>

<head>
  <meta charset="utf-8">
  <title>Newton's Cannon</title>
</head>

<body style="font-family:sans-serif; font-size:15px; width:600px; 
    margin-left:auto; margin-right:auto; 
    background-color:#e0e0e0;">

  <h1 style="font-size:24px; text-align:center;">
    Newton's Cannon
  </h1>

  <div style="width:500px; margin-left:auto; margin-right:auto;">
    <img src="NewtonDrawing.jpg" width="500" height="500">
  </div>

  <p>This simulation is based on Isaac Newton's famous thought
  experiment and illustration of firing a projectile from a high
  mountaintop at various speeds, as he described in
  <a href="http://books.google.com/books?id=DXE9AAAAcAAJ">A 
  Treatise of the System of the World</a>.</p>

</body>

</html>

In the following steps you will bring this page to life with animated graphics.

1. Add a place to draw.

The HTML5 canvas element provides an initially blank rectangular space where you can draw using low-level graphics instructions. Here’s the syntax for inserting a canvas:

<canvas id="theCanvas" width="500" height="500">
  Canvas not supported; please update your browser.
</canvas>

The content that you put between the opening and closing canvas tags will be displayed only in older browsers (most notably Internet Explorer 8 and earlier) that don’t support canvas. As with img, the width and height attributes are given in units of nominal screen pixels. The id attribute is simply a unique name by which your code can identify this canvas.

Please insert this code in your web page, right after the img element (and inside the div). Then test it, and note that your page now has a 500-pixel-high blank space just below the image. That’s the canvas, but we want it in front of the image, not underneath.

To move the canvas in front of the image, you need to use CSS absolute positioning. Simply insert style="position:absolute;" in the img tag, and style="position:relative;" in the opening canvas tag. Although it isn’t necessary here, I also recommend adding position:relative; to the style of the enclosing div.

Basically, position:absolute tells the browser to go ahead and place the element as usual, but then back up and place the next element as if the absolutely positioned element weren’t there. The explanation of position:relative (relative to not setting the position property at all) is more technical, but it works. In any case, please make these changes now and check that the page layout looks the same as it did before the canvas was there.

2. Draw on it using JavaScript.

You now have an invisible space where you can draw. To actually draw on it, you need to add some JavaScript to your page.

JavaScript is a programming language that you can use to manipulate anything and everything on a web page, changing the page from mere static information into a dynamic program (usually with user interactivity). To insert JavaScript code into a page you use the script element, like this:

<script>
  // JavaScript code goes here.
</script>

The script can be placed inside either head or body, but I usually put it in the latter, after all of the page’s visible content. So please do that, and then put the following JavaScript code inside the script:

var theCanvas = document.getElementById("theCanvas");
var theContext = theCanvas.getContext("2d");
theContext.beginPath();
theContext.arc(300, 50, 5, 0, 2*Math.PI);
theContext.fillStyle = "red";
theContext.fill();

The first line searches your page for an HTML element with id="theCanvas", and gives your JavaScript code access to this element through a variable of the same name. (You could use a different name for the variable, and actually most browsers will create a variable with the same name for you by default, but it’s a good idea to make it explicit.) The second line is needed for technical reasons: It gives you access to what’s called the canvas’s graphics context, which is what you use for the actual drawing. Once you have that, the next four lines draw a red filled circle on the canvas, centered at pixel coordinates (300, 50), with the second coordinate measured down from the top. The radius of the circle is 5 pixels, and the last two arguments of the arc function specify the beginning and ending angles (in case you ever want to fill a pie slice instead of a full circle).

Go ahead and test this JavaScript code, then try changing the location and size and color of the red circle. (Besides common color names like "red", you can use hexadecimal codes of the form "#rrggbb", just as in CSS.)

These six lines of JavaScript illustrate several important features of the language’s syntax:

In fact, most of the syntax of JavaScript is ultimately borrowed from C, and from its descendants such as C++ and Java, so if you already know one of these languages, you won’t need to learn much new syntax. However, the higher-level features of JavaScript are quite different from these other languages and may take more getting used to. For example, all JavaScript variables are declared using the var keyword, rather than by specifying a particular data type such as int or double. This is because JavaScript variable types are determined automatically as your program runs—a feature that many of us find unsettling.

3. Put it in motion.

Our next goal is to put that red dot (which will represent the launched projectile) into motion.

First, insert two more lines of JavaScript, somewhere before beginPath, to define general coordinates x and y for the center of the dot:

var x = 300;
var y = 50;

Then, in the line that calls the arc function, change the constants 300 and 50 to x and y. These changes should have no effect by themselves, but now you can move the four graphics instructions into a self-contained function that will work for any x and y values. To create your own function in JavaScript you use the function keyword (analogous to var for variables), like this:

function drawProjectile() {
    theContext.beginPath();
    theContext.arc(x, y, 5, 0, 2*Math.PI);
    theContext.fillStyle = "red";
    theContext.fill();
}

(Again the syntax is taken from C, using curly braces to group together the statements that are part of the function definition.) In addition, you need another function that will change the values of x and y. Let’s call this one moveProjectile:

function moveProjectile() {
    x += 1;
    drawProjectile();
    window.setTimeout(moveProjectile, 1000/30);
}

Here the first statement adds 1 to the value of x (you could alternatively write x = x + 1); the second statement calls the drawProjectile function; and the third statement tells your browser to call the moveProjectile function again in 1000/30 milliseconds (so it will be called about 30 times per second). This is the “trick” that keeps the animation going.

You still need to get the process started, by calling moveProjectile once from outside of any function:

moveProjectile();

(Put this statement right after your variable initializations.) When you test this code, you should see the red dot being drawn repeatedly at rightward-moving locations across the window, right up to the edge of the canvas.

To merely move the red dot, erasing it from its previous location, you can just erase the whole canvas at the beginning of the drawProjectile function:

theContext.clearRect(0, 0, theCanvas.width, theCanvas.height);

(The clearRect function can be used more generally to erase any rectangular area, where the parameters are the left, top, width, and height, respectively, all in nominal pixel coordinates. Here you also see that the theCanvas object provides width and height variables that you can use as needed.)

Again, be sure to test this change to your code.

This method of erasing and redrawing the projectile might seem cumbersome, especially compared to higher-level graphics environments in which you could simply change the values of x and y and see the object move automatically in response. That higher-level approach is called retained-mode graphics, and it’s more convenient for animating the motions of a limited number of objects. The canvas element, instead, implements what’s called immediate-mode graphics, in which each shape is immediately drawn as pixels and only the pixels themselves are stored in the computer’s memory. Immediate-mode graphics provides better performance for complex images that cannot be reduced to a few hundred (or fewer) discrete lines or polygons. Should you ever want a retained-mode environment for your animations, you can learn one of the JavaScript libraries that implements retained-mode graphics on top of canvas.

4. Put in the physics.

Of course you don’t want the projectile to move in an arbitrary way; you want it to move in response to earth’s gravity. So please remove the statement x += 1 from your moveProjectile function and replace it with some physics calculations, starting with these variable definitions:

var newtonG = 6.67e-11;        // grav. constant in SI units
var earthMass = 5.97e24;       // kilograms
var dt = 5;                    // time step in seconds
var r = Math.sqrt(x*x + y*y);
var accel = newtonG * earthMass / (r * r);  // magnitude of a

Here you see how to enter numbers in scientific notation and how to do some simple arithmetic, including a square root. Note that JavaScript (like C and Java) has no exponentiation operator, so to square a number you just multiply it by itself. As in Java and C++, the double-slash indicates the start of a comment, which continues to the end of the line. I should also explain that by declaring these new variables (with var) inside your moveProjectile function you are making them local to that function, so they aren’t available to any of the rest of your code, and their values disappear between successive calls to the function. The alternative is to make a variable global, which you do by declaring it outside of any function (as you did with x and y). It’s a good habit to make variables local whenever possible. (Unfortunately, JavaScript doesn’t actually require you to declare variables at all—but any undeclared variables automatically become global, and this can result in buggy code. Please declare all your variables!)

The last line of code above calculates the magnitude of our projectile’s acceleration, using Newton’s law of universal gravitation. (The projectile’s mass cancels out.) Notice how I’ve assumed that x and y are now measured in meters, from the center of the earth. We can find the x and y components of the acceleration vector with the aid of a sketch:

From the similar triangles you can see that the x component of the acceleration vector is minus its magnitude times the ratio x/r, and similarly for the y component. The code to compute these components is therefore:

var ax = -accel * x / r;
var ay = -accel * y / r;

From these acceleration components you can now update the components of the projectile’s velocity, and then use these to update the position:

vx += ax * dt;
vy += ay * dt;
x += vx * dt;
y += vy * dt;

I’ve already chosen a dt value of 5 seconds, which is quite short compared to the natural time scale for a low-earth orbit (on the order of an hour). But because dt isn’t infinitesimal, this algorithm predicts the projectile’s motion only approximately. The particular approximation used here is sometimes called the Euler-Cromer algorithm; it improves upon the standard Euler algorithm by using the new velocities, rather than the old ones, to update the positions.

Besides inserting all this code into your moveProjectile function, you also need to declare and initialize the velocity variables. Since their values need to persist through multiple calls to moveProjectile, you should declare them at the global level:

var vx = 6000;   // meters per second
var vy = 0;

While you’re at it, you’d better properly initialize the position:

var earthRadius = 6371000;   // meters
var mountainHeight = earthRadius * 0.165;  // chosen to match image
var x = 0;
var y = earthRadius + mountainHeight;

Finally, in your drawProjectile function, you need to convert the physical coordinates into canvas pixel coordinates:

var metersPerPixel = earthRadius / (0.355 * theCanvas.width);
var pixelX = theCanvas.width/2 + x/metersPerPixel;
var pixelY = theCanvas.height/2 - y/metersPerPixel;

The number 0.355 in the first line is chosen to match the image, and the minus sign in the last line is because the pixel coordinate is measured down from the top. Now just use pixelX and pixelY instead of x and y in the arc function (there’s no need to round these values to integers), and it should all work. Try it!

One piece of physics that’s still missing is the earth’s hard surface. To stop the simulation when the projectile hits the ground, all you need to do is enclose most of the code in moveProjectile in an if statement:

var r = Math.sqrt(x*x + y*y);
if (r > earthRadius) {
    var accel = newtonG * earthMass / (r * r);
    // and so on...
}

(Let’s continue to neglect the rather dramatic effects that earth’s atmosphere would have on a projectile moving several kilometers per second!)

Of course, the whole point of the simulation is that for sufficiently high initial speeds, the projectile will never hit the ground. In that case you probably want to stop it after a complete orbit. You can do so by saving the old value of x just before you update it, and then testing whether x has changed from negative to positive:

var lastx = x;
x += vx * dt;
y += vy * dt;
drawProjectile();
if (!((lastx < 0) && (x >= 0))) {
    window.setTimeout(moveProjectile, 1000/30);
}

(Note the use of the logical operators && for and and ! for not.)

5. Debug JavaScript errors.

It would be astonishing if by this time you haven’t made at least one typographical or other error while entering your JavaScript code. In that case, you may have found that the code simply fails to run, with no apparent explanation.

Fortunately, you can get an explanation in such cases by viewing your browser’s JavaScript console. The way to view it varies with different browsers; it may be called something like Web Inspector, perhaps under a special Developer menu that you have to enable. If you haven’t already, take the time to find it now, and then check out what it shows when you introduce a fatal error into your code (perhaps misspelling one of the variable names). The console should display an error message, the most important part of which is the line number of the offending code. This would also be a good time to configure your text editor to display line numbers in the left margin, if it isn’t already doing so.

Of course, the more troublesome bugs are those that don’t generate error messages but still cause your program to produce incorrect results. To diagnose those, you can use the console.log function to send your own diagnostic messages to the console:

console.log("Entering drawProjectile function");
console.log("Current position is " + x + ", " + y);

The second example also illustrates how the + operator can be used to concatenate text strings, and how JavaScript automatically converts numbers to strings when necessary.

Review

This was a long section of the tutorial, but it covered a lot of material: the JavaScript programming language, the HTML5 canvas element, and an algorithm for integrating Newton’s second law to predict the motion of a projectile.

I’ve prepared one-page reference sheets on JavaScript and Canvas graphics, which give an overview of the features covered here and a number of other useful features. The example pages that accompany this tutorial also demonstrate several features of these technologies.

For more comprehensive references, you can try W3Schools and also the HTML5 Canvas Tutorials site. Stack Overflow is again an essential resource. The definitive book on JavaScript is David Flanagan’s JavaScript: The Definitive Guide, which includes a chapter on canvas graphics. A nice book that’s dedicated to the latter is Core HTML5 Canvas by David Geary.

To learn more about solving Newton’s second law (and other differential equations) on a computer, some of the best textbooks are Numerical Methods for Physics by Alejandro Garcia, Computational Physics by Giordano and Nakanishi, and Introduction to Computer Simulation Methods by Gould, Tobochnik, and Christian.

Next: User Interaction

Back to index

Copyright © 2014, Daniel V. Schroeder