Web DevCenter
oreilly.comSafari Books Online.Conferences.
MySQL Conference and Expo April 14-17, 2008, Santa Clara, CA

Sponsored Developer Resources

Web Columns
Adobe GoLive
Essential JavaScript

Web Topics
All Articles
Scripting Languages

Atom 1.0 Feed RSS 1.0 Feed RSS 2.0 Feed

Learning Lab

Cross-Browser Animation
Pages: 1, 2, 3

Smoothing Out Those Complicated Paths

Using the array method shown above can lead to jerky movement. The number of points in your array will determine the jerkiness of the path; the more points, the smoother the movement. But putting lots of points into the array can be a hassle as it may take a long time to calculate each point. A quicker and more elegant solution is to use the points in the array as anchors, and then use setTimeout() to smoothly move between the anchor points. This won't always give exactly the path you want, but often it's good enough. This technique is demonstrated in the busy bee example.

If you take a look at the source of the example you'll see it involves a bit more code than the others. There are two main functions: getAnchors() and moveDiv().

The getAnchors() function looks up the beginning and ending points of each segment of the path and feeds these numbers to the moveDiv() function. The moveDiv() function moves the image using the setTimeout() trick discussed earlier. moveDiv() also checks that the image doesn't move past the ending anchor point. If the image is about to move past the end of the segment, moveDiv() moves the image to the ending anchor point and then calls getAnchors() to get the next pair of anchors.

The getAnchors() function is simple. It takes the current segment of the path as a parameter, and retrieves the appropriate beginning and ending points (the anchors). If the array_position is zero, it looks up the first anchor (the_coords[0]) and the second anchor (the_coords[1]):

var first_anchor = the_coords[array_position];
var second_anchor = the_coords[array_position+1];

Then the function adds one to the array_position variable and makes sure it hasn't reached the end of the array. If it is the end of the array, the function resets the position to 0, which means the animation will start over:

if (array_position == the_coords.length-1)
  array_position = 0;

After determining the next segment, the function calls moveDiv() to actually move the DIV:

moveDiv(array_position, first_anchor, second_anchor, 0, 0);

The first three variables sent to moveDiv() indicate, respectively, the current array element, the coordinates of the first anchor, and the coordinates of the second anchor. The final two parameters keep track of how far to move the DIV horizontally and vertically. Initially, you won't know what those values are, so you can just put zeros there. It's up to the moveDiv() function to figure out how much to move the DIV each time.

The moveDiv() function is more complicated than the getAnchors() function. First, it gets the stylesheet information using getStyleObject(). The next lines get the left and top coordinates of each of the anchors. Here's the code for the first anchor:

    var first_points = anchor_one.split(":");
    var first_left = parseInt(first_points[0]);
    var first_top = parseInt(first_points[1]);

Here's split() again, dividing the anchor, which looks like "100:120" into two numbers.

After that, moveDiv() calculates the horizontal and vertical step sizes, if they need to be calculated. They only need to be calculated at the beginning of each segment, when moveDiv() is called by getAnchors() with left and top step sizes set to zero. The getStepSize() function returns a number, which determines how much the DIV should move in that direction each time moveDiv() is called. getStepSize() takes three parameters: the coordinates of the two anchors and a number which indicates whether we want to know about the horizontal step size or the vertical step size (0 for horizontal and 1 for vertical):

    if ((horizontal_step_size == 0) && (vertical_step_size == 0))
      horizontal_step_size = 
        getStepSize(anchor_one, anchor_two, 0);

      vertical_step_size = 
        getStepSize(anchor_one, anchor_two, 1);

I'll describe getStepSize() in greater detail soon. Once the step size has been determined, the new left and top coordinates are calculated by adding the step size to the DIV's current location:

    var new_left = first_left + horizontal_step_size;
    var new_top = first_top + vertical_step_size;

Before the DIV is moved you have to make sure you're not overshooting the ending anchor of the segment. This is actually a bit trickier than you might initially guess. I wrote a separate function called atEndOfPath() to figure out whether or not the DIV is about to overshoot the ending anchor. The atEndOfPath() function needs to know the step size, the position of the end of the segment, and where the moveDiv() function wants to move the DIV. Based on these numbers, it returns true if the DIV is about to move past the end of the segment, or false if it's not. If it's about to move past the end of the segment, the DIV should instead move to the end anchor.

    if (atEndOfPath(left_step_size, second_left, new_left) 
       ||(atEndOfPath(top_step_size, second_top, new_top)))
      new_left = second_left;
      new_top = second_top;

Now, all you have to do before moving the DIV is to add "px" at the end of the coordinates, unless the page is being rendered by Netscape Navigator 4:

    if (!document.layers) 
      new_left = new_left + "px";
      new_top = new_top + "px";

And finally, you can move the DIV:

    the_style.left = new_left;
    the_style.top = new_top;

But even after the DIV has moved, there's more work to do. You have to figure out what the script should do next. There are two situations. If the script has reached the end of the segment, it should call getAnchor() to start working on the next segment. If the script hasn't reached the end of the segment, then it should call moveDiv() again in a short time to move the DIV along the segment a bit.

The first part of this is easy:

    if ((parseInt(new_left) == parseInt(second_left)) && 
            (parseInt(new_top) == parseInt(second_top)))

If the DIV is the end of the segment, it calls getAnchors() to get the next set of anchors. The second part is slightly trickier. You have to call moveDiv() again with the correct parameters. The call to moveDiv() has five parameters: how far into the path array we are, the position from which movement starts, the place where it ends, the horizontal step size, and the vertical step size. Something like this:

moveDiv(1, '120:100', '140:100', 10, 0);

The last three lines of the function create the string, and then use setTimeout() to call the function in a hundredth of a second:

   var new_anchor_one = new_left + ":" + new_top;

     var timeout_string = "moveDiv(" +
        array_position + ", '" + new_anchor_one + "', '" +
        anchor_two + "', " + left_step_size + "," + 
        top_step_size + ");";

      the_timeout = setTimeout(timeout_string, 10);

Here's the function which determines whether or not the DIV is about to go past the segment boundary:

function atEndOfPath(the_step_size, second_number, new_number)
    var the_end = false;

    if (((the_step_size > 0) && (new_number > second_number)) ||
        ((the_step_size < 0) && (new_number < second_number)))
     the_end = true;

   return the_end;

This function takes parameters representing the amount the DIV should move each time, the end of the segment, and the location the DIV is about to move to. There are two conditions that need to be considered. If the_step_size is positive, that means the DIV is moving right or down, so the DIV will have gone too far if the new_number parameter (the next step in the movement) is greater than the second_number. On the other hand, if the_step_size is negative, that means the DIV is moving to the left, or up, so the DIV has gone too far if new_number is less than second_number.

The last function figures out the step size and is pretty simple: it simply gets the relevant coordinate of the first and second point, and divides the distance between them by 10. This means that each segment will be broken up into 10 pieces, and moveDiv() will be called 10 times per segment.

And that is that. As you can see, it's quite a bit more complicated than any of the examples we saw earlier. However, it's a pretty good solution to the general problem of smoothly moving DIVs along complicated paths.

Time to Get Animated

Animation can take on many forms, but they generally follow a similar model. Create one or more images and play them in succession at a speed that makes it seem that one object is moving smoothly. The techniques you've learned here can apply to many effects, from scrolling menus to making superman fly across your screen. It takes a little practice, but once you get the hang of it, the possibilities are limited only by your imagination.

Dave Thau has been creating Internet applications since 1993, starting with bianca.com, the first web-based community on the Internet. Since then he's written The Book of JavaScript, acted as Director of Software Engineering and Senior Scientist at Wired Digital, and taught programming languages to hundreds of artists, engineers and children.

Return to the Web Development DevCenter.