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:
ID of the positioned element (as a string)
x coordinate of the starting position
y coordinate of the starting position
x coordinate of the ending position
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
page—you'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.
|