reptile7's JavaScript blog
Tuesday, July 18, 2006
An Image Loop: Allegro
Blog Entry #45

Having put together a slide show in our last episode, we turn our attention in this entry to another classic image-flip application - an animation - as we tuck into HTML Goodies' JavaScript Primers #28, "Putting it all together: Animation." The Primer #28 Script will look familiar - it does little more than add to the Primer #27 Script a pair of nested for loops (in an attempt) to automate the image-flip process.

In the "Script's Effect" section of the primer, Joe hints that the Primer #28 Script is not so generally useful when he says, "Due to changes in browsers, this script may not run on your MSIE browser." In practice, my varied attempts to execute the script - at least per its original, for-loop-based design - using either MSIE 5.1.6 or Netscape 7.02 were unsuccessful. So here's what we'll do:
(1) We'll first discuss how the script is supposed to work and why its execution is problematic; then,
(2) I'll give you a new-and-improved animation script that will work with any JavaScript-capable browser.

The Primer #28 Script

<script type="text/javascript">
var num=1;
img1 = new Image(150,150);
img1.src = "pic1.gif";
img2 = new Image(150,150);
img2.src = "pic2.gif";
img3 = new Image(150,150);
img3.src = "pic3.gif";
function startshow( ) {
for (i=1; i<20; i=i+1) { // for loop #1
for (x=1; x<800; x=x+1) { } // for loop #2
if (num==4) {num=1;} } // end of for loop #1
document.mypic.src=img1.src; }
<img src="pic1.gif" name="mypic" border="0" alt=""><p>
<a href="javascript:startshow( );">Display Animation</a>

There are no new concepts in the script above - we covered for loops in Blog Entry #40, and the remaining code (image preloading, the JavaScript URL, etc.) was addressed in the previous post - so let's get right to a rundown of what happens:

(1) When the document loads, the script in the document head preloads the pic#.gif images into the user's browser cache and the <img src="pic1.gif" name="mypic" border="0" alt=""> element in the document body displays the pic1.gif image as the first animation frame.

(2) When the user clicks the "Display Animation" link, then the href="javascript:startshow( );" code triggers the startshow( ) function in the document head script.

(3) The outer for loop #1 begins. Each iteration of for loop #1 displays an animation frame, and thus for loop #1's condition - i<20 in the primer script and source code, but i<21 in the 2nd and 6th <li>s of the script deconstruction - determines the total number of frames in the animation.

(4) At the start of for loop #1's first iteration, the value of the indexing variable num, unchanged since its global initialization on the first line of the document head script, is 1, and the document.mypic.src=eval("img"+num+".src") command thus reloads the pic1.gif image.

(5) The inner for loop #2 executes, "counting" to 800 and serving (in theory) as a time-delay between consecutive animation frames.

(6) (a) The num=num+1 statement increments num to 2.
(b) The condition of the if (num==4) {num=1;} statement is false, so the browser skips over the {num=1;} command.
(c) Lastly, for loop #1's counter variable i increments to 2 to wrap up for loop #1's first iteration.

(7) For for loop #1's second iteration:
(a) With num=2, assignment of "pic2.gif" to document.mypic.src displays the pic2.gif image as the second animation frame.
(b) The inner for loop #2 again counts to 800.
(c) The num=num+1 statement increments num to 3.
(d) The if condition is still false.
(e) The loop counter i increments to 3.

(8) For for loop #1's third iteration:
(a) With num=3, assignment of "pic3.gif" to document.mypic.src displays the pic3.gif image as the third animation frame.
(b) The inner for loop #2 again counts to 800.
(c) The num=num+1 statement increments num to 4.
(d) The if condition is now true and num is reset to 1.
(e) The loop counter i increments to 4.
At the start of for loop #1's fourth iteration:
(f) With num=1, assignment of "pic1.gif" to document.mypic.src redisplays the pic1.gif image as the fourth animation frame.

Steps (5)-(8) then repeat* until the end of the loop, i.e., when the value of i reaches 20 or 21 at the end of the 19th or 20th loop iteration, respectively. A 19- or 20-iteration loop will display not-quite-seven pic1.gif-pic2.gif-pic3.gif image cycles and will end on the pic1.gif and pic2.gif images, respectively.
(*Excluding steps (6)(c), (7)(e), and (8)(e) - i continues to increment, of course.)

(9) Finally, the document.mypic.src=img1.src statement following for loop #1 redisplays the pic1.gif image as the last animation frame.

So what's the problem?

We turn our attention to for loop #2, about which (in the script deconstruction's 4th <li>) Joe says:

"Notice that there are no statements in the 'for' loop. The loop itself handles counting from 1 to 800. That equals about 800/1000 of a second."

800 milliseconds?? Oh goodness - my 1999 iMac smokes through an 800-iteration for loop in about five milliseconds when using MSIE, as determined by:

d0 = new Date( );
for (x=1; x<=800; x=x+1) { if (x == 800) d1 = new Date( ); } // d1-d0 returns 5-6

However, ramping up the iteration count to 160,000 - in the hope of creating a 1-second time-delay - does not help. Here's what happens in this case when using MSIE: I first click the "Display Animation" link; after 2-3 seconds, the normal arrow cursor becomes a spinning wait cursor (I specifically see the spinning cursor in Figure 11-1 on this page), signifying that an operation is in progress; about 15 seconds later, the wait cursor reverts to the arrow cursor; from start to finish, all I see for an "animation" is an unvarying display of the pic1.gif image. Netscape 7.02 gives similar results, but much more slowly (startshow( ) executes in ≅130 seconds for x=160000).

Joe helpfully points out in the Primer #28 Assignment answer that "alert is a good tool for debugging [i.e., troubleshooting] your programs," and we can verify that the parts of the startshow( ) function are indeed running via the use of strategically inserted alert( ) commands at various points in the function, for example:

function startshow( ) {
window.alert("The startshow( ) function has been triggered.");
for (i=1; i<20; i=i+1) { if (i==5) window.alert("The 5th iteration of for loop #1 is underway.");
for (x=1; x<=160000; x=x+1) { if ((i==5) && (x==160000)) window.alert("Yes, for loop #2 really does count to 160,000."); }
// the && logical/Boolean AND operator is discussed here
if (num==4) {num=1;} }
document.mypic.src=img1.src; }

Our diagnosis, then, is that for loops #1 and #2 mesh in such a way that prevents the separative display of the pic#.gif images. At the least, it is clear that for loop #2 does not stall the operation of for loop #1 (even as we were somewhat successful in using bodyless for loops to stall background color changes for the Primer #24 Assignment, as discussed in Blog Entry #40).

To muddy the water a bit before moving on, I find when using Netscape 4.79 that the Primer #28 Script works just fine if for loop #2's iteration count (x) is set to 25,000.

A new-and-improved animation script

So the nested for loop business doesn't work very well. However, we can ditch the for loops altogether via a judicious use of the setTimeout( ) method of the window object, as follows:

<!--replace the document head script in the Primer #28 Script with the code below-->
<script type="text/javascript">
var num=1;
var i=1;
img1 = new Image(150,150); img1.src = "pic1.gif";
img2 = new Image(150,150); img2.src = "pic2.gif";
img3 = new Image(150,150); img3.src = "pic3.gif";
function startshow( ) {
if (num==4) num=1;
if (i<22) window.setTimeout("startshow( );",1000);
if (i==22) i=1; }

Try it out below:

Display Animation

The script above retains the features of the original for loop #1, namely; (i) a var i=1 initial-expression that precedes the startshow( ) function; (ii) an updated if (i<22) window.setTimeout("startshow( );",1000) condition placed in the startshow( ) body; and (iii) an i=i+1 increment-expression, which is also in the startshow( ) body. The window.setTimeout("startshow( );",1000) command of the if statement recursively calls the startshow( ) function after 1000 milliseconds, providing a ≅1-second delay between animation frames. Netscape notes that "setTimeout does not stall the script. The script continues immediately (not waiting for the timeout to expire). The call simply schedules a future event." The window.setTimeout("startshow( );",1000) command is thus placed near the end of the startshow( ) function so that it doesn't interfere with startshow( )'s other statements. The script concludes with an if (i==22) i=1 statement that resets i to 1 at the end of the animation in case the user wants to run the animation again. One would think that the if (i<22) window.setTimeout("startshow( );",1000) expression could be replaced with: if (i<22) { for (x=1; x<=160000; x=x+1) { if (x == 160000) startshow( ); } } but in the event this substitution gave the spinning-wait-cursor-unvarying-pic1.gif-image behavior described above for the execution of the nested for loops in the original script. The Primer #28 Assignment In the Primer #28 Assignment, Joe asks the reader to modify the Primer #28 Script so that a user can enter slow, medium, or fast into a text box and then run the pic#.gif animation at a corresponding speed. At this point, as you can imagine, my patience with for-loop-based animation scripts has run its course, and for this reason I'm not going to discuss Joe's answer script here. Here's a demo of my own assignment answer, which again uses a window.setTimeout( ) command to delay the display of the pic#.gif images and substitutes a set of radio buttons for the text box.
Choose an animation speed: Fast Medium Slow
Animate me!
At a non-Blogger page I would use this code for the above demo; its deconstruction is left to you.
In the next post, we'll look at HTML Goodies' JavaScript Primers #29, in which we'll get an introduction to the important topic of data validation.


Comments: Post a Comment

<< Home

Powered by Blogger

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