DHTML Lab - dhtmlab.com - Smooth animation using DHTML | 5 | WebReference

DHTML Lab - dhtmlab.com - Smooth animation using DHTML | 5

Logo

Smooth animation using DHTML
part 2 - creating the test


When I first sat down to create a test setup I tried to ensure that it was simple and gave me the necessary data I needed. I wanted the test to contain code that could be used in a production setup while still making sure things worked well enough. It wasn't very difficult to come up with a usable idea.

My test plan

My plan was to have a layer containing a small image. I would then position the layer in the top left corner of the browser window, and move it one pixel at a time around the edges of the browser window. Before the layer started moving I would check the time and then check it again when the layer had returned to the initial position. The difference in time would then reflect the speed it had traveled.

My thought was that the script couldn't finish faster than the layer could move. That way the time difference would reflect the actual time spent, not the time spent calling the function that moves the layer. Since I moved the layer one pixel at a time I would easily know how far it was around the whole browser window, and therefore know what kind of delay the script actually used. This theory not only sounded like a good idea, it also worked well when used in practice.

Accuracy problems?

Some of you are probably curious about any interference available in JavaScript's time checking. While writing my own delay-function I did actually check the time it takes for JavaScript to create a new Date() object. You can find the actual test in the test section and try it out for yourself. The test I ran creates 10,000 date objects, and on my system it takes about 1000ms. Therefore, creating one date object takes 0.1ms, which should be accurate enough for our testing purposes.

I've also taken care to create the start time and end time as closely as possible to the actual time the layer movement started and ended. The difference between them is then found by subtracting the start time from the end time. This result is in all tests written to a text input field in a form centered on screen. This setup has proven to give me good results after various tests of different JavaScript functions.

The actual test code

Lets have a look at the actual code used for testing. For my tests I took a screenshot of my desktop and created an image of my Netscape Communicator icon. The image is 69x62 pixels. I chose to put the image in its own layer. The test documents also contain another layer which in turn contains a small form controlling the test setup. The form has several input fields and a button. The button starts the test, and the input fields are mostly used for output related to the test setup. The fields used in the test are:

The canvas is the browser's visible document area. All data except the time the test took, is calculated immediately after the document is loaded. The document's onload event calls a function that calculates it all and then writes the values to the form. That way I get immediate knowledge of the test setup.

The first test I ran used setTimeout() to control the delay/animation-loop. I chose to create several global variables so that they would be available to all functions. That way I could make the onload event set the data that the layer-movement function will use later. The HTML I ended up with was this:

<HTML>
<HEAD>
<TITLE>Smooth animation using DHTML - setTimeout() test</TITLE>
 
<STYLE TYPE="text/css">
   #controls { position:absolute; z-index: 101; width: 250px; }
   #image { position:absolute; z-index: 100; width: 70px; }
</STYLE>
</HEAD>
<BODY onLoad="do_onload()"
      TOPMARGIN="0" LEFTMARGIN="0"
      MARGINHEIGHT="0" MARGINWIDTH="0">
<DIV ID="image"><IMG SRC="nc-icon.gif"></DIV>
 
<DIV ID="controls">
    <FORM NAME="my_form">
         # of times to run the test:
         <INPUT TYPE="text" SIZE="4" NAME="repeats"><BR>
         <INPUT TYPE="BUTTON" VALUE="Run test(s)"
                   onClick="begin_test()"><BR>
         Time it took (ms):
         <INPUT TYPE="text" SIZE="10" NAME="result"><BR>
         Canvas size:
         <INPUT TYPE="text" SIZE="5" NAME="win_width">
         X
         <INPUT TYPE="text" SIZE="5" NAME="win_height"><BR>
         Steps in X-direction:
         <INPUT TYPE="text" SIZE="5" NAME="x_steps"><BR>
         Steps in Y-direction:
         <INPUT TYPE="text" SIZE="5" NAME="y_steps"><br>
         Total steps for 1 test:
         <INPUT TYPE="text" SIZE="10" NAME="total_steps">
    </FORM>
</DIV>
</BODY>
</HTML>

I've omitted all scripts to make it easier to read. The code is very simple. There's one layer for the form which I've called "controls.". The layer with the image is simply called "image." I initially position both in the upper left corner. As you can see the HTML is very simple to make sure it won't affect the test too much.

I then add scripting. First I created a function to move the form layer into position. I've positioned it in the center of the browser window so the image layer can move around it. I've written several cross-browser functions to help me do this. I've got two functions for retrieving the width & height of the browser's document area, and two functions for retrieving the width & height of a layer. I also have a small function to add two properties to the Netscape layer object; width & height. I also have the already mentioned cross-browser moveTo() function. These functions actually make up the majority of the script code.

The onload event is used to do several tasks. First it sets the height & width property of both layers if the browser is Netscape Navigator. It retrieves the height & width of the browser's document area and moves the form layer to the middle of it. Then it calculates the actual steps needed to move the layer in the X- & Y-direction, and lastly writes all the information to the form fields in the form layer. Here is some of the code, slightly changed for readability. All code is available in the tests shown in the test section:

<SCRIPT LANGUAGE="JavaScript1.2" TYPE="text/javascript">
var myRepeats = 1; // times to run the test
var curX = 0; // current X-position
var curY = 0; // current Y-position
var direction = "right";
// current direction (right, left, up, down)
var winHeight; // height of browser window, set onload
var winWidth; // width of browser window, set onload
 
var start_time;
var end_time;
 
var xSteps;
var ySteps;
function do_onload() {
 
  winWidth = myWinWidth(self);
  winHeight = myWinHeight(self);
 
  xSteps = (winWidth-myLayerWidth('image'));
  ySteps = (winHeight-myLayerHeight('image'));
 
}
</SCRIPT>

If you've been paying attention to the code so far you'll see that I've created global variables for most things I want to know. This is important. Since I'm trying to test layer movement, knowing this information beforehand and actually doing simple tests with the already existing variables will be quicker than checking the layer size, window size and current position every time.

What remains are three functions created for the first test. One function starts the test, one function ends the test, and one function moves the layer. The function that starts the test pulls the number of times to run the test from the form, then creates the start_time Date() object and calls the function that moves the layer. The code that finds the number of times to run the test can be omitted (again for readability) so we end up with:

<SCRIPT LANGUAGE="JavaScript1.2" TYPE="text/javascript">
function begin_test() {
  start_time = new Date();
  stepLayer('image',"right");
}
</SCRIPT>

This function is called by the button in the form. Similarly, we have a function that ends the test. Actually, it doesn't end the test. It's called when the test is finished. It creates the end_time Date() object, calculates the actual time it took to finish, and then writes it to the correct text field in the form. A very simple function (if we leave out the form writing):

<SCRIPT LANGUAGE="JavaScript1.2" TYPE="text/javascript">
function end_test() {
  end_time = new Date();
  total_time = end_time - start_time;
  // form write should be here, of course :)
}
</SCRIPT>

The last function is the one that actually moves the layer. In my first test I chose to use setTimeout() for the animation/loop. The function is very simple to create. It's called with two parameters, the ID of the layer you want to move, and the direction to move it. The function checks the current x- & y-position and the direction which are all global variables. It uses the well-suited switch() statement and ends with a call to myMoveTo(). The whole code looks like this:

<SCRIPT LANGUAGE="JavaScript1.2" TYPE="text/javascript">
function stepLayer(layerID, direction) {
  var end = false;
  switch(direction) {
    case "right":
      if(xSteps-curX > 0) {
        curX++;
      } else {
        direction = "down";
        curY++;
      }
      break;
    case "down":
      if(curY < ySteps) {
        curY++;
      } else {
        direction = "left";
        curX--;
      }
      break;
    case "left":
      if(curX > 0) {
        curX--;
      } else {
        direction = "up";
        curY--;
      }
      break;
    case "up":
      if(curY > 0) {
        curY--;
      } else {
        if(myRepeats > 1) {
          myRepeats--;
          direction = "right";
          curX++;
        } else {
          end_test();
          end = true;
        }
      }
      break;
  }
  if(!end) {
    myMoveTo(layerID, curX, curY);
    setTimeout("stepLayer('" + layerID + "','" + direction + "')",40);
  }
}

The function checks the current direction, checks whether it's reached the end of that direction or not, increments/decrements a variable, and then if it's not finished calls myMoveTo() with the new position. In this example the delay is set to the borderline value of 40ms, in the actual tests it varied from 1ms to 40ms.

As you might be able to spot, when it reaches the top left corner for the last time it calls end_test() and makes sure the call to setTimeout() isn't done again.

Variations

After running the initial test using setTimeout() I created three variations of the test. I'll discuss two of them later, the setInterval() and myDelay() versions. I also created one version of the script that simply used a for()-loop to do the movement. Since it has no delay it would reveal whether the browsers have trouble moving the layer, or whether the problem lay elsewhere. All versions of the script use the same function for actual movement, but some of them have the setTimeout() call commented out. Let's look at the for()-loop test.


Produced by Morten Wang and

All Rights Reserved. Legal Notices.
Created: Jan 03, 2000
Revised: Jan 03, 2000

URL: http://www.webreference.com/dhtml/column28/part-2.html