After finishing the previous section of this tutorial, you should have a fully functional simulation of Newton’s Cannon. If it’s good enough for you, you can now put this simulation aside and move on to another HTML5 project. On the other hand, if you’re a perfectionist like me, you might first want to refine the Newton’s Cannon simulation in a few ways.
Each of the following refinements can be made independently, so feel free to skip any that don’t interest you.
The original Newton’s Cannon illustration shows the paths of the projectiles, so perhaps yours should too.
The best way to add “trails” to this simulation is to draw them on a separate canvas, sandwiched in between the existing canvas and the underlying image. You can do this with just four additional lines of code:
img
and canvas
elements,
to add the second canvas. Give it id="trailCanvas"
, with the same dimensions
as the others and position:absolute
.trailCanvas
,
otherwise identical to the one for theCanvas
.trailContext
), analogous to theContext
.drawProjectile
function, a line to draw a small dot
at the projectile’s current location. This is easiest if you actually make the
dot a rectangle, because there’s a one-line convenience function for drawing
rectangles:
trailContext.fillRect(pixelX-0.5, pixelY-0.5, 1, 1);The first two parameters are the coordinates of the rectangle’s upper-left corner, so I’ve subtracted 0.5 to center it precisely; the third and fourth parameters are the rectangle’s width and height.
The new canvas’s fillStyle
defaults to black, but feel free to change
this to a different color if you prefer.
When you test these changes, you should find that the dots are close enough
together to form a continuous curve. (If they weren’t so close, and you still wanted
a continuous curve, you could use the moveTo
and lineTo
functions to draw lines. This would also require a couple of new global variables to store the
projectile’s previous location, and a bit of code to ensure that you don’t
connect the end of one path to the beginning of the next.)
Notice that the canvas can accumulate an unlimited number of trails, with thousands upon thousands of individual dots, with absolutely no performance penalty. This is one advantage of immediate-mode graphics over retained-mode graphics.
It’s also a nice touch to add a button to clear the trails and start over.
Add a line of HTML code for this new button, analogous to that for the “Fire!”
button. Set the onclick
attribute to call a new function called clearTrails
, and have this function
call trailContext.clearRect
in a way analogous to the first line of your drawProjectile
function.
Newton’s illustrator used shading to give the planet a three-dimensional appearance.
You can do the same for your projectile by filling it with a radial gradient instead of
a solid color. In your drawProjectile
function, just replace the line that
sets fillStyle
with the following four statements:
var theGradient = theContext.createRadialGradient( pixelX-1, pixelY-2, 1, pixelX, pixelY, 5); theGradient.addColorStop(0, "#ffd0d0"); theGradient.addColorStop(1, "#ff0000"); theContext.fillStyle = theGradient;
The parameters of the createRadialGradient
function define two circles,
in terms of their center locations and radii (here 1 and 5, respectively). The
addColorStop
functions then specify the colors on (and beyond) these circles,
and the gradient automatically interpolates between them. Try changing the gradient
metrics and colors until you’re happy with the appearance.
Choosing optimum sizes for things is tricky in a web app, because you don’t know
the user’s screen size. There are several reasons, though, why you might want to make
buttons bigger than their default sizes. A quick-and-dirty way to enlarge them is
to specify a larger font via the CSS font-size
property. But I’ve found
that styling buttons is tricky, producing inconsistent results in different browsers.
A robust, but cumbersome, alternative is to avoid buttons entirely and instead just use links. The HTML code for your Fire! button would then be:
<a class="customButton" href="javascript:void(0)" onclick="fireProjectile();" ontouchstart="">Fire!</a>
Try this out and check that the “button” still works. I don’t actually understand
the reason for javascript:void(0)
, but seemingly knowledgable people
recommend
it. The empty ontouchstart
attribute is a detail that improves the behavior on
mobile devices.
The class
attribute is a convenient way of applying the
same styling to multiple elements. To make it work, you define the class inside a
style
element in the head
portion of your source file.
After much fiddling, I’ve settled on the following styling for my custom
buttons:
<style> .customButton { /* style a link as a push-button */ display: inline-block; width: 60px; height: 24px; line-height: 24px; font-size: 15px; font-family: sans-serif; text-align: center; color: black; background: -webkit-linear-gradient(white,#eeeeee,#eeeeee,#e0e0e0); background: linear-gradient(white,#eeeeee,#eeeeee,#e0e0e0); text-decoration: none; border: 1px solid gray; border-radius: 5px; -webkit-user-select: none; -moz-user-select: -moz-none; -ms-user-select: none; user-select: none; cursor: pointer; -webkit-tap-highlight-color: rgba(0,0,0,0); } .customButton:active { background: -webkit-linear-gradient(#909090,#808080,#808080,#707070); background: linear-gradient(#909090,#808080,#808080,#707070); } </style>
Yes, it’s cumbersome, and I don’t claim that it’s perfect.
I hope you can guess what most of these CSS properties do. The gradients use various
gray levels to give
the buttons a nice 3-D appearance. (You can, of course, use brighter colors if you
don’t think they’ll be too distracting.)
Some of the property names are nonstandard, specific
to particular browsers or groups of browsers (Webkit, Mozilla, and Microsoft).
The dot before customButton
indicates that we’re defining a class,
and the curly braces enclose all the property settings that belong to that class.
The active
pseudo-class changes the button’s color while it is
being pressed.
Slider controls look a little different in each different browser—with the exception of Internet Explorer, in which they look a lot different. In IE the default width is much larger, and the default “padding”, or extra space, above and below the slider is absurdly large. IE also puts ugly tick marks along the slider’s track, and puts a pop-up numerical readout, rounded to the nearest integer, above the slider while you’re adjusting it.
Here’s the styling that I use to fix all these issues (formatted for
use inside the style
element in your page’s head
):
input[type="range"] { width: 140px; padding: 0px; } input[type="range"]::-ms-tooltip { display: none; /* hide readout in IE */ } input[type="range"]::-ms-track { color: transparent; /* hide tick marks in IE */ }
Even with these changes, sliders will look very different in IE than in other browsers. But the remaining differences are primarily a matter of taste, and there’s something to be said for keeping the appearance consistent within each browser.
You may have noticed that if you reduce the launch speed below 1000 m/s, all the GUI controls shift as they are re-centered to accommodate the loss of a digit in the speed readout. If you find this behavior annoying, you can easily fix it with a bit of styling:
<span id="speedReadout" style="display:inline-block; width:2.3em; text-align:right;">
Specifying the width in em
units (each equal to the font size in
pixels) is more robust than using pixel units if you later decide to change the font size.
But this fix
is still a bit of a kludge, because I determined the optimum width by trial and error
and the number is somewhat font-dependent.
If you now view the page on a smartphone, however, you’ll see that
the readout looks funny because the number is in a smaller font than the
text around it. That’s because smartphones automatically enlarge font
sizes for readability when a “block” (such as a paragraph or a div
)
is wider than a certain amount (typically
320 pixels). Giving the readout a fixed width required making it its own block,
so now it doesn’t get enlarged. The best fix, at least for the most common
mobile browsers, is probably to turn off the
automatic enlargement by inserting
-webkit-text-size-adjust:100%;
into the style of the enclosing div
.
As a final tweak to the appearance of the line of GUI controls, you can insert a little extra space to separate logically distinct elements. One way to do this is by adjusting the left and right margins, but a slightly easier method is to insert two or three “non-breaking space” characters,
into the HTML just after the Fire! button (and two or three more just before the Clear button, if you’ve implemented that).
Another nice aesthetic touch is to change the straight typewriter-style apostrophe in Newton's to a pretty typographer’s apostrophe (or right single quote):
’
You can use similar syntax in your HTML to insert a wide variety of special characters, inluding math symbols, Greek letters, and accented letters. Each code begins with an ampersand and ends with a semicolon, with a unique few-letter sequence in between. Several of the most useful are listed on the accompanying HTML and styling reference sheet, which also provides a link to a complete list.
On touch screen devices, touching an element on a web page can sometimes make the
browser think you want to select it for copying. That makes sense for text, but not
for most GUI controls. I’ve found this behavior particularly annoying for sliders,
so I generally put the following CSS into the <style>
element in my
document’s header:
input { -webkit-user-select: none; -moz-user-select: -moz-none; -ms-user-select: none; user-select: none; }
Another issue with mobile devices is that they need to know or assume a value for the
full width of your web page in pixels—and on iOS (at least), this number defaults to an
annoyingly large value of 980. (I try to keep the content of my pages considerably narrower, so they
won’t monopolize my laptop screen or exceed the width of an 800-pixel projector.)
Fortunately, you can change this default by
putting another meta
tag into your page’s header:
<meta name="viewport" content="width=640">
As long as users are holding their devices in portrait orientation, the optimum setting for the content width is just a little more than the width of your page’s body (or widest block). The down-side is that this makes your page harder to use in landscape orientation, because users probably won’t be able to zoom out far enough to see all the important parts at once. Try it, in any case, and decide what setting best suits your layout and the uses you have in mind.
I normally subscribe to the philosophy that computers should be seen and not heard. But the Java version of Newton’s Cannon that inspired this tutorial has some cute sound effects that are kinda fun, at least for a little while. So I’ve tried to implement sound effects in my HTML5 version of Newton’s Cannon, and they work fine on traditional computers, but not on my iOS devices. There should exist a fix for the latter, but I haven’t yet found the time to implement and test it. If and when I do, I’ll describe the solution here.