reptile7's JavaScript blog
Monday, April 04, 2011
Pong It
Blog Entry #210

Before we get rolling, let me correct a mistake in the previous post. Just before the Animation #1 section, I said, At no point does the tutorial detail the ball.gif image's underlying HTML; in fact, that HTML does appear at the very end of the Animation #4 code on the tutorial's third page, sort of: the id value (ball2) is wrong and the src attribute points to a .png image and not a .gif image, but it is there. (Curiously, at the WebReference version of the tutorial, the second page works with a ball.png image but the third page works with the ball.gif image.) However, I still feel that this code should be specified earlier and given greater prominence in the tutorial.

We continue today our analysis of HTML Goodies' "How to Create a JavaScript Animation" tutorial. We pick up the conversation at the Defining The Area Of Motion section of the tutorial's second page, which presents the tutorial's third animation example.

Animation #3

Like Animation #1, Animation #3 moves the ball.gif image horizontally from left to right. Unlike Animation #1, Animation #3 stops the image movement at the right edge of the document/viewport (not "the screen"); towards this end, Animation #3 supplements the goRight( ) function with an animBall( ) function that conditions the image movement on the position of the image's left edge. The animBall( ) function relies on a separate computeWin( ) function to determine the width of the document/viewport; we'll see below that the computeWin( ) function is superfluous and contains a serious mistake that would pose a problem for the tutorial's fourth animation if the clientHeight property were not as widely supported as it is currently.

Clicking the Animation #3 Bring the Ball Here... demo anchor drops the image to top:2660px; at the HTML Goodies version of the tutorial and to top:2060px; at the WebReference version of the tutorial (the demo works at both versions, but it's easier to follow at the latter - the 2660px value for the former is larger than it should be). Clicking the adjacent Try It link

<a href="javascript:window.setInterval('animBall( )', 80);">Try It</a>

calls the animBall( ) function every 80 milliseconds.

function animBall( ) {
    imgLeftInt = parseInt(document.images["ball1"].style.left);
    ... }

animBall( ) first extracts the integer at the beginning of the image's style.left value and then gives that number an imgLeftInt identifier. animBall( ) next reads the width of the image and gives that number an imgWidth identifier.

imgWidth = parseInt(document.images["ball1"].width);

The parseInt( ) operation is unnecessary* as document.images["ball1"].width references a DOM property with a long (number) data type and not a CSS property with a string data type.
(*It's also inefficient: what actually happens is that parseInt( ) initially converts the document.images["ball1"].width value to a string and then extracts the original number from that string - see the Description section of Mozilla's parseInt( ) page.)

The next animBall( ) line reads the width of the document/viewport and gives that number a winWidth identifier.

winWidth = parseInt(computeWin( ).windWidth);

This line calls the computeWin( ) function, which
(a) equips the window object with a custom, cross-browser windWidth property for getting the width of the document/viewport, and then
(b) returns the window object.
The windWidth value has a number data type so we don't need a parseInt( ) operation here either.

Here's the computeWin( ) function:

function computeWin( ) {
    if (document.body.clientWidth) {
        this.windWidth = document.body.clientWidth;
        this.windHeight = document.body.clientHeight; }
    else {
        this.windWidth = window.innerWidth;
        this.windHeight = document.innerheight; }
    return this; }

The author discusses the computeWin( ) function in very general terms:
What if we want [the ball] to go to the edge of the screen [document/viewport] and just stop? Well, for that, we need to know where the edge of the [document/viewport] is. Fortunately, there are ways of finding out. The bad news is not all browsers use the same methods to tell us what we need to know.

Notice that we don't bother asking what kind of a browser the user has. That's just a good way to write code that becomes obsolete as soon as you post it to your site. Instead, we ask whether the browser understands what we need. If it doesn't understand the first time, we assume it uses the second method to give us the answer.
No Microsoft-vs.-Netscape specifics of any kind are provided - that's where we come in.

For MSIE 4, Microsoft introduced clientWidth and clientHeight properties for respectively getting the width and height of an object/element; these properties can be applied to most HTML elements. When applied to the body element, clientWidth and clientHeight respectively retrieve the width and height of the document content area; if the document content area does not exceed the viewport, then document.body.clientWidth and document.body.clientHeight will respectively return the viewport's width and height. At about the same time, Netscape implemented in JavaScript 1.2 innerWidth and innerHeight properties for the window object that directly measure the viewport's width and height, respectively. All of these properties are on track to be standardized in a CSSOM View Module specification.

computeWin( )'s if clause initially tests browser support for document.body.clientWidth; if we've got that support, then document.body.clientWidth and document.body.clientHeight are respectively assigned to custom this.windWidth and this.windHeight properties. The author only describes the this reference as an object; however, a window.alert(this); command reveals that this takes on the identity of the window object ([object DOMWindow] is displayed on the alert( ) box).

Once upon a time, document.body.clientWidth would have returned undefined and thus would have converted to false as an if condition for non-MSIE users, but this is no longer the case. All of the OS X GUI browsers on my computer support document.body.clientWidth and document.body.clientHeight; moreover, I can confirm via tests in the SheepShaver environment that Netscape/Mozilla support for these properties began with Netscape 7.

For its part, computeWin( )'s else clause assigns window.innerWidth to this.windWidth but assigns document.innerheight to this.windHeight. innerHeight is not and has never been a property of the document object, and its H is capitalized; the capitalization mistake is corrected in the balls.js script but the document reference is not. Nevertheless, if we were using a browser for which the else statements were operative, then the document.innerHeight line would not interfere with Animation #3, whose horizontal movement calls on the windWidth property but not the windHeight property.

(window.innerWidth and window.innerHeight are supported by most of the non-MSIE browsers on my computer, and it is simple enough to comment out the if clause and the else { } container and force these browsers to go through the else statement pathway. The document.innerHeight line does give rise to strange behavior at the bottom of the document for Animation #4 - I'll spare you the details.)

The this references in the computeWin( ) function are unnecessary: windWidth and windHeight could be defined as freestanding variables, which would effectively make them properties of the window object. But windWidth and windHeight are themselves excess baggage; indeed, we can throw out the entire computeWin( ) function if we define winWidth and a corresponding winHeight via:

winWidth = document.body.clientWidth ? document.body.clientWidth : window.innerWidth;
winHeight = document.body.clientHeight ? document.body.clientHeight : window.innerHeight;

Anyway, with imgLeftInt, imgWidth, and winWidth in hand, let's move to the conditional that concludes and lies at the heart of the animBall( ) function:

if (imgLeftInt < (winWidth - imgWidth)) {
    goRight( ); }
else {
    window.clearInterval(t); }

The ball.gif image has a width of 30 pixels. The (winWidth - imgWidth) expression in the if condition effectively defines a 30-pixel-wide 'stop zone' abutting the right edge of the document content area. As long as the left edge of the image (imgLeftInt) is to the left of the stop zone, then the image is moved rightward by the goRight( ) function:

function goRight( ) { document.images["ball1"].style.left = parseInt(document.images["ball1"].style.left) + 5 + "px"; }

When the image's left/right edges coincide with those of the stop zone, the else clause halts the movement. (In practice, the ball stops a bit beyond the stop zone, or at least that's what I see on my computer.)

Try it out below - click the button and then the button:

Animation #4
Let's put it all together. We can make the ball go up and down, left and right, and random speeds. We can start the animation and stop it with a click.
Animation #4 significantly ramps up the complexity of the ball.gif movement.

• Building on Animation #3, the author introduces binary dirx/diry variables that determine the direction of motion

function animBall(on) { // No use is made of the on argument.
    if (dirx == 1) // dirx = 1 signifies left-to-right motion.
        goRight( );
    else // dirx = 0 signifies right-to-left motion.
        goLeft( );
    if (diry == 1) // diry = 1 signifies up-to-down motion.
        goDown( );
    else // diry = 0 signifies down-to-up motion.
        goUp( );

and send the ball in the opposite direction when it hits a given edge of the document content area.

if (imgLeftInt > (winWidth - imgWidth)) { // If the image goes beyond the document's right edge...
    dirx = 0; // Send it leftward.

• In Animations #1, #2, and #3, the ball moves at a predetermined, uniform speed. In Animation #4, a setRand( ) function

function setRand( ) {
    randnum = Math.floor(Math.random( ) * 6) + 2;
    return randnum; }

sets a random value for the ball's initial speed (more precisely, the Δx part of the ball's Δx/Δt)

var spdx = setRand( );
function goRight( ) {
    document.images["ball1"].style.left = imgLeftInt + spdx + "px";

and sets a new random value for the ball's speed when it changes direction.

if (imgLeftInt > (winWidth - imgWidth)) {
    dirx = 0;
    spdx = setRand( ); }

The setRand( ) function is explained in the A Little Random Action section of the tutorial's second page, to which I would make a minor, nitpicking correction: both Mozilla and Microsoft note that Math.random( ) can in fact return 0.

• Whereas Animations #1, #2, and #3 are purely horizontal animations, Animation #4 simultaneously combines horizontal and vertical movement. Accordingly, the goRight( ) and goLeft( ) functions are supplemented with corresponding goDown( ) and goUp( ) functions that iteratively get and set the ball's value.

As intimated at the outset of the post, the full Animation #4 code is reproduced on the tutorial's third page. It's easier to grab the code at the WebReference version of the tutorial; click the "view plain" link at the top of the display and then copy the code from the window that pops up. For whatever reason, at the HTML Goodies version of the tutorial the code was taken out of its original textarea container, which would have made it easy to copy, and annotated with never-used line numbers, which must be removed before execution.

Directly above the code are Click here and Start Animation links for activating an Animation #4 demo. Clicking the Click here link visibilizes the ball at a position:absolute;left:10px;top:10px; style address. At the WebReference version of the tutorial, clicking the Start Animation link calls the Animation #4 animBall( ) function, which is not externalized but appears in the document source, every 80 milliseconds via a window.setInterval( ) command. At the HTML Goodies version of the tutorial, the document source does not include or otherwise call on the Animation #4 animBall( ) function but, embarrassingly, imports the same balls.js script that is imported by the preceding page; as a result, clicking the Start Animation link repeats the Animation #3 demo via calling the Animation #3 animBall( ) function.

Even at the WebReference version of the tutorial, and assuming the computeWin( ) if clause to be operative (vide supra), the demo is a bit of a chore to follow because
(a) the demo motion is bottom-bounded by document.body.clientHeight and
(b) the document height greatly exceeds the viewport height.
So I guess I should give you my own demo.

At any given time, the goRight( ) or goLeft( ) function is running in tandem with the goDown( ) or goUp( ) function. The horizontal and vertical components of the ball's motion are completely independent of one another - that's why the ball moves as it does. Besides making the recommended changes of the Animation #3 section above, I have recast dirx/diry as dirX/dirY à la the DOM's coordinate-based properties (e.g., pageX, screenX) and, drawing inspiration from the HTML dir attribute, switched their 1/0 values to ltr (left to right)/rtl (right to left)/utd (up to down)/dtu (down to up) as appropriate, but have otherwise left the original Animation #4 code alone.
The next Beyond HTML : JavaScript sector tutorial, "Using Multiple JavaScript Onload Functions", presents an addLoadEvent( ) function for triggering multiple functions with a single load event and without successive window.onload = functionPointer; statements overwriting one another. My first impression was that the addLoadEvent( ) code is a solution in search of a problem, but the author states, We use it over at the JavaScript Source for all of our scripts requiring the onload event handler. After that, we have "How to Use a JavaScript Query String Parser", which was covered in Blog Entries #195 and #196. After that is "JavaScript and HTML Tricks", which we'll check over in the following entry. reptile7

Comments: Post a Comment

<< Home

Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)