After finishing the previous section of this tutorial, you should have a working web simulation of Newton’s Cannon, with animated graphics. But the simulation runs just once when the page loads, and the projectile’s initial velocity is hard-coded to a fixed value. Your next task is to hand the control of the simulation over to the user.
First let’s add a “Fire!” button to start the simulation. To put a button
into your page you can use the HTML input
element, like this:
<input type="button" value="Fire!">
Go ahead and insert this code, below the div
that contains the image and the
canvas, but above the paragraph of text, and check that the button appears. To center the
button, put it inside its own div
with a style
attribute
that sets text-align:center
.
(You could put the button inside a paragraph instead of a div
, but it’s best
to reserve paragraphs for text.)
Next you need a JavaScript function to call when the button is pressed. This function
should initialize the position and velocity variables, then call moveProjectile
to get the simulation started:
function fireProjectile() { x = 0; y = earthRadius + mountainHeight; vx = 6000; vy = 0; moveProjectile(); }
The position and velocity variables still need to be declared at the global level (so their values persist after this function exits), but those declarations can now be shortened to a single line:
var x, y, vx, vy; // position and velocity
To call fireProjectile
when the button is pressed, you can simply
set the button’s onclick
attribute inside its HTML tag:
<input type="button" value="Fire!" onclick="fireProjectile();">
Now, when you load the page, you can fire the projectile repeatedly. Try it!
But there’s a slight problem: If you impatiently click the Fire! button again,
before the projectile has landed, the simulation will restart and will now run twice
as fast as before. This is because you’ve called moveProjectile
again
while another call to moveProjectile
is still pending via the most recent
setTimeout
, so you end up with two calls to moveProjectile
either
pending or running at any given time. You can fix this bug by storing the result of
setTimeout
in a variable:
timer = window.setTimeout(moveProjectile, 1000/30);
Declare this timer
variable (“var timer;
”)
at the global level, and then insert
the following line at the beginning of fireProjectile
:
window.clearTimeout(timer);
Finally it’s time to let the user adjust the projectile’s initial speed. The best way to do this is with a slider control:
<input type="range" min="0" max="8000" step="100" value="3000">
Insert this tag right after the one for the button, inside the same div
,
and check that it shows up in your browser. (Support for this HTML5 feature has been slow
in coming to certain browsers, most notably Firefox, which didn’t implement it until
August 2013.) The attribute meanings should be self-explanatory; I’ve chosen
their values based on my personal judgment, which you should feel free to override.
To access the slider’s value from your JavaScript code, you need to set its id
attribute:
... type="range" id="speedSlider" min="0" ...
Then declare and initialize a global variable with the same name:
var speedSlider = document.getElementById("speedSlider");
Now you can simply replace the line in fireProjectile
that sets vx
with the following:
vx = Number(speedSlider.value);
(The Number
function forces JavaScript to convert the value from a character
string to a number immediately, rather than later when you try to do arithmetic with it.
This doesn’t matter here, but I’ve found that it can sometimes dramatically affect
performance so I consider it a good habit. The reason why the value is a string to begin with
is rooted in the conventions used for all input
controls.)
Once again, be sure to test your code after making these changes.
Unfortunately, the slider doesn’t automatically come with a numerical readout to show the user its value. (Internet Explorer actually does provide a readout, but it has some limitations and in any case, you don’t want to assume that all your users are running Internet Explorer.) Fortunately, you can add a readout pretty easily.
Start by adding the following line of content to your HTML, in between the button and the slider:
Initial speed = <span id="speedReadout">3000</span> m/s
The span
element is the in-line version of div
; it does nothing
inherently to its content, but lets you change the styling or, in this case, assign an
id
. To access this element from JavaScript, put it into a variable as usual:
var speedReadout = document.getElementById("speedReadout");
Next, define a function that sets the content of the readout to the slider’s value whenever it is called:
function showSpeed() { speedReadout.innerHTML = speedSlider.value; }
Now you can just add calls to this function as attributes inside the slider’s HTML tag:
... value="3000" oninput="showSpeed();" onchange="showSpeed();">
(Why two different attributes? The first, oninput
, is supposed to “fire”
whenever the user moves the slider’s thumb, while the second,
onchange
, is supposed to fire only when the thumb is released. But there has been
some inconsistency among browsers in implementing these features, so I’m in the habit
of using both, just to be safe.)
Just as most web developers frown upon the use of in-line styling via the style
attribute, they also tend to frown upon the use of in-line JavaScript via attributes
like onclick
and onchange
. Apparently their view is that
all JavaScript code should be segregated in its own separate file, rather than being
mixed in with the HTML. That’s actually pretty easy to do, but I frankly don’t
see any advantage to it for the types of applications discussed here.
I’ve prepared a User interface reference sheet that summarizes the syntax not only for buttons and sliders, but also for other common types of graphical user-interface features such as checkboxes, drop-down menus, and direct mouse (or touch) events on a canvas. The examples that accompany this tutorial demonstrate each of these interaction mechanisms.
If you look up user-interface controls in a more general HTML reference site or book,
you’ll find them in the section on forms. That’s because these controls
were originally intended for use on forms that users fill out in order to send information
back to web servers. Fortunately, the technologies are sufficiently powerful that we client-side
web programmers have successfully co-opted them. Just don’t be surprised if the examples
that you find elsewhere are cluttered with extraneous <form>
tags and references
to “submit” buttons.
Next: Finishing Touches