13.9 Animating Straight-Line Element Paths

NN 6, IE 5

13.9.1 Problem

You want to animate the position of an element from one coordinate to another on the page along a straight-line path.

13.9.2 Solution

To animate a positioned element along a straight-line path, link the animeLine.js library (Example 13-2 in the Discussion) to your page and invoke the initSLAnime( ) function with at least the first five parameters in the following sequence:

  1. ID of the positioned element (as a string)

  2. x coordinate of the starting position

  3. y coordinate of the starting position

  4. x coordinate of the ending position

  5. y coordinate of the ending position

For example:

initSLAnime("floater", 100, 100, 400, 360);

13.9.3 Discussion

While you can move a positioned element along a hardwired straight-line path in far less code than shown in this recipe, the animeLine.js library shown in Example 13-2 provides a generalizable solution, in which you specify the ID of the element to move, the start and end coordinates, and the relative speed. The custom objects and functions perform all of the necessary math to accomplish the job, regardless of direction or length of travel.

Example 13-2. The animeLine.js library for straight-line animation
// animation object holds numerous properties related to motion
var anime;
   
// initialize default anime object
function initAnime( ) {
    anime = {elemID:"", 
             xCurr:0, 
             yCurr:0, 
             xTarg:0, 
             yTarg:0, 
             xStep:0, 
             yStep:0,
             xDelta:0,
             yDelta:0,
             xTravel:0,
             yTravel:0,
             vel:1, 
             pathLen:1, 
             interval:null
            };
}
   
// stuff animation object with necessary explicit and calculated values
function initSLAnime(elemID, startX, startY, endX, endY, speed) {
    initAnime( );
    anime.elemID = elemID;
    anime.xCurr = startX;
    anime.yCurr = startY;
    anime.xTarg = endX;
    anime.yTarg = endY;
    anime.xDelta = Math.abs(endX - startX);
    anime.yDelta = Math.abs(endY - startY);
    anime.vel = (speed) ? speed : 1;
    // set element's start position
    document.getElementById(elemID).style.left = startX + "px";
    document.getElementById(elemID).style.top = startY + "px";
    // the length of the line between start and end points
    anime.pathLen = Math.sqrt((Math.pow((startX - endX), 2)) + 
    (Math.pow((startY - endY), 2)));
    // how big the pixel steps are along each axis
    anime.xStep = parseInt(((anime.xTarg - anime.xCurr) / anime.pathLen) * anime.vel);
    anime.yStep = parseInt(((anime.yTarg - anime.yCurr) / anime.pathLen) * anime.vel);
    // start the repeated invocation of the animation
    anime.interval = setInterval("doSLAnimation( )", 10);
}
   
// calculate next steps and assign to style properties
function doSLAnimation( ) {
    if ((anime.xTravel + anime.xStep) <= anime.xDelta && 
        (anime.yTravel + anime.yStep) <= anime.yDelta) {
        var x = anime.xCurr + anime.xStep;
        var y = anime.yCurr + anime.yStep;
        document.getElementById(anime.elemID).style.left = x + "px";
        document.getElementById(anime.elemID).style.top = y + "px";
        anime.xTravel += Math.abs(anime.xStep);
        anime.yTravel += Math.abs(anime.yStep);
        anime.xCurr = x;
        anime.yCurr = y;
    } else {
        document.getElementById(anime.elemID).style.left = anime.xTarg + "px";
        document.getElementById(anime.elemID).style.top = anime.yTarg + "px";
        clearInterval(anime.interval);
    }
}

The library begins by defining a blank anime object, which becomes the holding place for each animation you invoke. Each invocation of the main initSLAnime( ) function sets initial values of the anime object to zero, and then populates the properties with values passed as parameters or values calculated from the parameters. The final act of this intermediate initialization of the straight-line animation process ends with a setInterval( ) function, which invokes the actual animation function repeatedly. The interval identifier is also preserved as a property of the animation object, rather than having an extra global variable cluttering up the document.

The repeated doSLAnimation( ) function moves the visible object toward its destination until the element approaches or reaches the target coordinates. When it can go no further, the element is explicitly positioned at the destination, and the interval identifier is cleared to stop the iterations.

All coordinate values are in the page-absolute coordinate system, as these values are ultimately assigned to the positioned element to start and end the animation. If you wish to align the start and/or end points of the animated element with a body content element, use Recipe 13.8 to determine the absolute position of the fixed-location element, and then use those values as parameters to initSLAnime( ). The straight-line path can be at any angle (including perfectly vertical or horizontal) and in any direction. But be careful about specifying coordinates that are beyond the browser window's width: you can lose an element that is off the page and out of view.

If you want to animate an element to follow a more complex path, you can string together invocations of the initSLAnime( ) function, but be sure to allow each setInterval( ) iteration to complete its task. The simplest way to begin is to assign one more property to the anime object, called next, and initialize it to zero. Because the initAnime( ) function is called from a different place in the script (and repeatedly), this new property value must preserve its value from one leg to the next. Thus, the object property assignment statement lets an existing value persist into the next initialization:

...
next: (anime.next) ? anime.next : 0,
...

Next, create a controlling function that contains an array of coordinate pairs for the start and end positions of each straight line comprising your complex path. For example, here is a version that provides three paths to make up a triangle:

function animatePolygon(elemID) {
    // prepare anime object for next leg
    initAnime( );
    // create array of coordinate points
    var coords = new Array( )
    coords[0] = [200, 200, 100, 400];
    coords[1] = [100, 400, 300, 400];
    coords[2] = [300, 400, 200, 200];
    // pass next coordinate group in sequence based on anime.next value
    if (anime.next < coords.length) {
        initSLAnime(elemID, coords[anime.next][0], coords[anime.next][1], 
        coords[anime.next][2], coords[anime.next][3], 10);
        // increment for next leg
        anime.next++;
    } else {
        // reset 'next' counter after all legs complete
        anime.next = 0;
    }
}

Other changes include moving the initAnime( ) function call from initSLAnime( ) to the new animatePolygon( ) function. Finally, in doSLAnimation( ), after the interval is cleared following a completion of one leg, invoke the animatePolygon( ) function again to get the next leg going, passing the element ID from the current anime object:

animatePolygon(anime.elemID);

Employ as many coordinate points as you like to provide the animation path. You do not have to return the element to its original position if you don't want to.

An optional sixth parameter, an integer, represents the relative velocity of the animation. Speed is influenced by the size of the jump during each iteration of the doSLAnimation() function. The default value is 1, which is pretty slow. Users may expect a little more animated motion, for which a value of 10 works nicely. But you can experiment with your content and layout to find the optimum speed. You can also experiment with the second parameter of the setInterval( ) method (with much higher numbers than shown in the solution) to move in small steps at even slower speeds.

Be aware that the perceived speed of the animated element will not be constant across all browsers or computers. This is not animation in the same sense as time-based media displayed in dedicated environments such as Flash. The trade-off over the lack of temporal precision is that you can animate standard HTML elements and have the animated elements overlay other content anywhere on the pageyou're not limited to the embedded Flash rectangle.

13.9.4 See Also

Recipe 13.8 for obtaining coordinates of elements within the body; Recipe 3.7 for creating a custom object; Recipe 13.10 for animation in a circular path.