JAVASCRIPT ANIMATION
Object Movement
JavaScript makes web page content dynamic allowing user interaction and simple animation. The principle of JavaScript animation is rather simple; web page elements, most often images, have their the style or position within the page altered by a script. Repeating this process at sufficiently short intervals has the effect of simulating smooth motion of the element about the screen.
Each of the elements comprising a web page is defined by an object (an arrays of data) stored as part of the page's Document Object Model (DOM). The elements rendered on the screen are all rectangles defined by values for their width and height and their position is set by values for the coordinates of the element's upper left corner. To manipulate these values a reference to the element's DOM object must be generated. The simplest way to do this is to make sure the element has an 'id' attribute and then use the document.getElementById() method.
The image of the red ball on the right is defined as follows:
<img id="ball"
src="Images/redBall.gif"
style="position:absolute;
width:2em;
height:2em;
left:5em;
top:3em" />
and the JavaScript to reference its DOM object and change its left and top value is as follows:
<script type="text/javascript">
function moveBall()
{
var ballObj = document.getElementById("ball");
ballObj.style.left = "7em";
ballObj.style.top = "5em";
}
</script>
Press here to try the code.
Continuing Movement
To get repetitive movement, the window.setInterval() method can be used. This method takes a function and a time interval as parameters. setInterval waits for the time period and calls the function, then waits for the time interval and calls the function again and so on until the timer is cancelled.
In this example the stepBall2() function is called every 100msec. The global variable n2 stores the number of steps the ball has taken along its animation path. The function stepBall2() calculates the new values of left and top coordinates of the ball for the current step number, and then calls moveBall2() to actually make the ball move. The step number, n2 is incremented ready for the next call to stepBall() by the setInterval timer. Finally a check is made to stop and reset the animation after 13 steps.
<script type="text/javascript">
function moveBall2(l, t)
{
var ballObj = document.getElementById("ball2");
ballObj.style.left = l+"em";
ballObj.style.top = t+"em";
}
var timer2 = null;
var n2 = 0;
function stepBall2()
{
x = 5+0.4*n2;
y = 3+0.16*n2*n2; // a parabolic path y=x*x
moveBall2(x, y);
n2++;
if (n2>13)
{
clearInterval(timer2);
n2 = 0;
}
}
function startBall2()
{
timer2 = setInterval(stepBall2, 100);
}
</script>
Press here to try the code.
Object Oriented Animation
The same JavaScript animation functionality may be applied to several web page elements. They may have different algorithms to define their animation paths. This code reuse is ideally suited to Object Oriented programming.
The example below presents the basic object orient animation control code. In this case a simple circular path has been used and stop and start functions added.
function pos(x, y)
{
this.x = Math.round(x);
this.y = Math.round(y);
}
function animation(id, path, steps, tickInt)
{
this.elem = document.getElementById(id);
this.active = 0;
this.timer = null;
this.path = path; // pointer to path object
this.pathIdx = 0; // step counter
this.numSteps = steps; // total steps, 0 = go forever
this.tickInt = tickInt; // tick interval in msec
this.onStart = null; // hook for external action
this.onStop = null;
}
animation.prototype.start = function()
{
if (this.active)
return;
var savThis = this; // make a local copy for closure
this.pathIdx = 0; // start at beginning of path
this.step();
this.active = 1;
// create closure
this.timer = setInterval(function(){savThis.step()}, savThis.tickInt);
if (this.onStart)
this.onStart();
}
animation.prototype.resume = function()
{
if (this.active)
return;
var savThis = this;
// this.pathIdx resumes where it was stopped
this.step();
this.active = 1;
// create closure
this.timer = setInterval(function(){savThis.step()}, savThis.tickInt);
if (this.onStart)
this.onStart();
}
animation.prototype.stop = function() // same as pause, pathIdx not reset to 0
{
if (!this.timer)
return false;
clearInterval(this.timer);
this.active = 0;
if (this.onStop)
this.onStop();
}
animation.prototype.step = function()
{
var nextPos = this.path.nextStep(this.pathIdx);
this.moveTo(nextPos.x, nextPos.y);
if ((this.numSteps > 0) && (this.pathIdx >= this.numSteps))
this.stop();
else
this.pathIdx++;
}
animation.prototype.moveTo = function(x, y)
{
this.elem.style.top = y + 'px';
this.elem.style.left = x + 'px';
}
This code provides the animation control structure. A 'path' object is required. There may be as simple or complex as required but it must have a method named 'nextStep' which takes an integer representing the step number along the path and returns a 'pos' object representing the location to which the top left of the element should be moved.
// Circular path object
function circle(initPos, radius, angle0, stepSize)
{
this.rad = radius;
this.startAngle = angle0;
this.aStep = stepSize; // angle per step
this.cx = initPos.x - this.rad * Math.cos(angle0 * Math.PI / 180); // coords of centre
this.cy = initPos.y - this.rad * Math.sin(angle0 * Math.PI / 180);
this.currX = initPos.x;
this.currY = initPos.y;
}
circle.prototype.nextStep = function(index)
{
var angle = this.startAngle - index * this.aStep; // +ve angles are cw, we want ccw
this.currX = this.cx + this.rad * Math.cos(angle * Math.PI / 180);
this.currY = this.cy + this.rad * Math.sin(angle * Math.PI / 180);
return new pos(this.currX, this.currY);
}
All that remains is some initialisation code to create the objects. This code is run when the page is has finished loading.
function getCurrPos(id)
{
var docObj = document.getElementById(id);
return new pos(docObj.offsetLeft, docObj.offsetTop);
}
function initAnimation3()
{
var startPos = getCurrPos("ball3");
var startAngle = 0; // in degrees
var radA = 70; // radius of circle, in pixels
var step = 3; // angular step size, in degrees
var cirlePath = new circle(startPos, radA, startAngle, step);
anim3 = new animation("ball3", cirlePath, 200, 25); // 200 steps, 25msec tick interval
}
The stop and start buttons make the calls anim3.stop() and anim3.start().
A More Complex Oo Example
The advantages of the object oriented approach may not be so obvious in such a simple example. Here is a more complex example with instances of the animation object, one moving the red ball the other the blue ball. In this case a path object simulates free fall under gravity with reflection (bouncing) off the boundary box. The instances are given randomised initial conditions each time they are reset.
Another useful trick used here allows the path object to access the properties of its parent animation object. This is required since the path needs to calculate when the ball image will hit the boundary wall and so needs to access the animation.elem property to extract the image dimensions. It also needs to know the tick interval to calculate distance moved between ticks, again this is a property of the parent object. To allow access to the parent object the animation object is first created with the child (path) object set to null. The path object is then created with a reference to the parent object passed as a parameter. The animation.path is then set to reference the newly created path object. The relevant code snippet is as follows:
blueAnim = new animation("ballBlue", null, 200, 25);
blueAnim.path = new freeFall(blueAnim, startPos, startAngle, speed, box);
As an example, the freeFall object can access the blueAnim.elem to calculate the width of the 'blueBall' image as follows:
... this.elementWidth = this.parentAnim.elem.offsetWidth; // image width in pixels