Monday, February 25, 2008

JavaScript Animation - Part 3 - The Code So Far

In the last JavaScript Animation post, we left off with code for the AnimationManager class. So far we can do is call addMovement, specifying a direction, the total number of pixels to move, and the number of pixels to move during each "frame". Then, when startAnimation is called, the manager will automatically daisy chain calls to the Window object's setInterval method. When the element specified in each movement instruction has reached it's destination, the next movement instruction is executed. Not bad for a start. But this doesn't yet allow us to do two rather useful things.


  • Move in two directions at once, i.e., diagonally or along a curve.

  • Move two elements at once.


But before we add new functionality, let's go over what we have so far.



A quick note before we begin. I prefer to use the object literal syntax for class declaration. In case you aren't familiar with it, all it means is that instead of adding member variables and functions to the hidden prototype object one at a time...

Foo.prototype.someMethod = function(){ ... };
Foo.prototype.some_int = 1;

I add them all at once...
Foo.prototype = {
some_int : 1,
someMethod : function(){ ... }
}

Not that different, but it is easier to read, is a bit more compact (fewer characters=smaller files), and much easier to type since you don't have to constantly be writing out prototype. Now there is a downside: since most of the JavaScript IDE plugins I've seen are not able to parse class structures when written this way, so they can't help in outlining the class for quick navigation. For example, the "Outline" view in Eclipse becomes useless, so you might want to stick with the more verbose style if that will slow you down too much.



The AnimationManager class begins with a few member variable declarations.

function AnimationManager(){
this.initialize();
}
AnimationManager.prototype = {

movements : undefined,
intervals : undefined,
current_movement_index : undefined,


The first two are arrays that will hold the movements we ask the manager to perform and the interval ids returned by the various calls to the Window object's setInterval method They are not initialized when declared, because though the AnimationManager class will most often be instantiated as a Singleton, you may want more than one at some point and we don't want them all sharing the same array. (I will talk more about this later). So instead they are initialized
in a method called, you guessed it, initialize.



initialize : function(){

this.movements = new Array();
this.intervals = new Array();
},


To those of you familiar with the Prototype JavaScript library, the initialize method is essentially a replacement for the classes constructor method. So when you call

Class.create({ // class definition using object literal syntax })

the initialize() method will be called as soon as the class is instantiated.
But since I am not using Prototype here, we will accomplish more or less the same thing by calling initialize()
in the contructor.



So on to the method that tells AnimationManager what to do. Since there are no private methods in JavaScript, we can't hide the rest of the class, but this is really the only method aside from startAnimation and stopAnimation that another class or script would ever have to access. (At least until we start adding functionality later.) The addMovement method takes four arguments,


  • element - the element we want to move around,

  • direction - the direction (up, down, left, right) we want element to move,

  • pixel_count - how many pixels to move it,

  • increment - how many pixels to move it from frame to frame of the animation.

The first three are self-explanatory, but increment might be a little confusing if you still haven't grasped the whole "frame" thing.More on that in a bit. For now, all we need to know is that these four values are put together into an anonymous object and stored (in order) along with any other instructions the manager may receive. But what about the other two variables being stored in the object - start_x and start_y? What are they for? We'll get to that in a moment.


addMovement : function( element, direction, pixel_count, increment ){

if( element ){

this.movements.push( {

element : element,
start_x : 0,
start_y : 0,
direction : direction,
pixel_count : pixel_count,
increment : increment

});

}else{
// element doesn't exist
return false;
}
},



Next we have nextMovement, which takes no arguments, but knows a couple of things about its containing class.

nextMovement : function(){

if( this.current_movement_index < this.movements.length ){

var movement = this.movements[this.current_movement_index];
movement.start_x= this.findPosX( movement.element );
movement.start_y = this.findPosY( movement.element );

...


The member variable current_movement_index is simply that, an index of the movement currently being executed. The list that it is associated with is movements, the array we stored all our movments in as they were added by an outside script or class. The first line of nextMovement asks if there are still movements left to be executed. If so, we go on to calculate and store the values for start_x and start_y. These are simply the starting x and y positions (in pixels) for the element we are about to start moving. These are used to check how much progress has been made with each successive call of executeMovement. Again, more about them later. Next we come to one of the more confusing parts of the whole project. It's actually not that complicated once you get the hang of it, but it can present a bit of a conceptual hurdle.



When you call a JavaScript function, the variable scope of that function is determined not by its declaration, but by its caller. So even if we create the class Foo and declare and define the method bar() as a member function, we cannot guarantee that, when it is called, a this pointer inside the scope of the method will actually point to an instance of the class Foo. In practice, this really only applies to callbacks.
Here is an example.

function Foo(){};
Foo.prototype = {
class_name : "Foo",
bar : function(){
alert( "bar belongs to " + this.class_name );
}
}

var foo = new Foo();
some_button.onclick = foo.bar;

When some_button is clicked, you will get the following alert message,
bar belongs to undefined

Why? Because when some_button is clicked, and it calls Foo's bar method, bar is executed inside some_button's scope, and some_button does not have a member variable named class_name. Basically some_button has stolen and co-opted bar(), and made it it's own.



But what, you may ask, does this have to do with the AnimationManager? Well let's have a look. First we will write the method in the same way as we did the Foo.bar() example.


...

this.intervals[this.current_movement_index] = window.setInterval( function(){

if( !this.executeMovement( this, new Array() ) ){

window.clearInterval( this.intervals[this.current_movement_index] );
this.current_movement_index++;
this.nextMovement( this, new Array() );
}

}, 30 );

return true;
}

},


This looks like a fairly straightforward example of a recursive function call. Each time setInterval is called, we check to see if executeMovement is through with its work, and if it is, we clear the interval and queue up the next movment. But run it and you won't get past the second line. The problem is this. Inside the anonymous function that will be run over and over by setInterval, the this pointer refers to the anonymous function object we created, which has no idea about what executeMovement or intervals might mean. It would simply tell the interpreter that they are undefined.



Now you might suggest pointing setInterval to a member function instead, but this would still result in the same error, only it would happen further up the call stack. In the same way some_button stole the scope of bar() away from Foo, so too would Window.setInterval steal the member function's scope away from AnimationManager.



So how do we deal with this? We need to maintain scope or we'll have to put the entire functionality of AnimationManager inside setInterval. The solution turns out to be an otherwise obscure native function called apply(). apply is a member of the native JavaScript function class, so any function object can use it. It takes two arguments.



  • thisArg - the object you want this to resolve to,

  • arguments - an array of arguments passed to the function you are calling.

So going back to the Foo.bar example, if we use apply(), it would look like this.
function Foo(){};
Foo.prototype = {
class_name : "Foo",
bar : function(){
alert( "bar belongs to " + this.class_name );
}
}

var foo = new Foo();
some_button.onclick = function(){
Foo.prototype.bar.apply( foo, new Array() );
}

Now if we click on some_button, the alert will say,
bar belongs to Foo


So applying (pardon the pun) the same principle to AnimationManager, we can write it like this.

    ... 

var ani_mgr = this;
this.intervals[this.current_movement_index] = window.setInterval( function(){

if( !AnimationManager.prototype.executeMovement.apply( ani_mgr, new Array() ) ){

window.clearInterval( ani_mgr.intervals[ani_mgr.current_movement_index] );
ani_mgr.current_movement_index++;
AnimationManager.prototype.nextMovement.apply( ani_mgr, new Array() );
}

}, 30 );
}

},

Now when executeMovement is called, the this pointer refers to the instance of AnimationManager we assigned to the local variable ani_mgr. We solve the same problem inside the anonymous function by substituting ani_mgr for this.



Notice that we set the time between executions of setInterval to 30ms, as per the formula mentioned in the
first post in the series.



Next we have executeMovement. It acts mainly as a router. It determines the direction of the movement being executed, and makes a call to the appropriate member function. If the element is to be moved to the right, it calls - you guessed it - moveRight. This is done using a simple switch-case enclosure, but to make things a bit more readable and yet avoid using cpu intensive string comparisons, I created a series of static member variables that can be used kind of like an Enumeration. The variable declarations are at the end of the class declaration, since static members are not generally declared as a part of the prototype object, and so do not have to be associated with a particular instance of the class.


executeMovement : function(){

var movement = this.movements[this.current_movement_index];
var element = movement.element;

if( element ){

switch( movement.direction ){

case AnimationManager.UP:
return this.moveUp( movement );
break;

case AnimationManager.RIGHT:
return this.moveRight( movement );
break;

case AnimationManager.DOWN:
return this.moveDown( movement );
break;

case AnimationManager.LEFT:
return this.moveLeft( movement );
break;

default: return false;
}
}else{

console.info( "no element for executeMovement" );
}


},


Next up we have the machinery of the AnimationManager.


  • moveUp - moves the element up

  • moveRight - moves the element right

  • moveDown - moves the element down

  • moveLeft - moves the element left





moveUp : function( movement ){

var destination = movement.start_y - movement.pixel_count;
var current_y = this.findPosY( movement.element );

if( movement.element && destination < current_y ){
movement.element.style.top = ( current_y - movement.increment ) + "px";
return true;
}else
return false;


},

moveRight : function( movement ){

var destination = movement.start_x + movement.pixel_count;
var current_x = this.findPosX( movement.element );

if( movement.element && current_x < destination ){
movement.element.style.left = ( current_x + movement.increment ) + "px";
return true;
}else
return false;
},


moveDown : function( movement ){

var destination = movement.start_y + movement.pixel_count;
var current_y = this.findPosY( movement.element );

if( movement.element && destination > current_y ){
movement.element.style.top = ( current_y + movement.increment ) + "px";
return true;
}else
return false;
},

moveLeft : function( movement ){

var destination = movement.start_x - movement.pixel_count;
var current_x = this.findPosX( movement.element );

if( movement.element && destination < current_x ){
movement.element.style.left = ( current_x - movement.increment ) + "px";
return true;
}else
return false;

},

I ordered them this way because they match the clockwise directional order you see in CSS classes: top, right, bottom, left. Better to be consistent than confused.



All four direction methods function in the same way: check the current position of the element against the place it's supposed to end up. If it hasn't reached it's destination yet move it increment pixels. The destination is determined based on the direction the element is supposed to move. If its moving to the right, executeMovement calls moveRight. Moving to the right means moving in a positive direction along the x-axis, so we can simply add the the pixel_count provided by addMovement to the element's start_x. So,

var destination = movement.start_x + movement.pixel_count;


To get the current_x position of the element, we use findPosX, which is based on the work of Peter-Paul Koch over at quirksmode.org. It is a cross-browser method for getting the pixel position of any element on the page. It is a little slow for work like this because it has to deal with the quirks inherent to the various browsers, but it works well, so I'm not about to mess with it at the moment. We'll go over the code for it later.
var current_x = this.findPosX( movement.element );

Now that moveRight still has work to do, it goes ahead and does it.


if( movement.element && current_x < destination ){
movement.element.style.left = ( current_x + movement.increment ) + "px";
return true;
}else
return false;
},

When the moving is done, the method returns true. Back in nextMovement, it tests the return value of the move to determine whether that move should be repeated,
or whether the next move should be executed. So returning true means do it again, while returning false means the element has reached it's destination and
current_movement_index should be incremented by one before calling nextMovement again.




Next we come to the on/off switch for AnimationManager. After adding movements, we have to call startAnimation to get things actually moving. It simply sets
current_movement_index to 0 (just in case we've already run through some movements), and gets things started by calling nextMovement. If at some point we want
to stop on a dime, we can call stopAnimation, which will make sure any and all setInterval calls are cleared using clearInterval.



startAnimation : function(){


this.current_movement_index = 0;


this.nextMovement();
},


stopAnimation : function(){

for( var i=0; i<this.intervals.length; i++ ){

window.clearInterval( this.intervals[i] );

}

},




Finnally, we have the Peter-Paul Koch-based methods for finding the x and y position of an element. Actually what it finds is the left and top positions of an element, but they are effectively the same thing. I won't go into how he does it, because you should have a look at his site anyway. Its an invaluable resource for web developers. Check it out.

findPosX : function( element ){

var curleft = 0;

if(element.offsetParent){
while(1){
curleft += element.offsetLeft;
if(!element.offsetParent)
break;
element = element.offsetParent;
}
}else if(element.x){
curleft += element.x;
}
return curleft;
},


findPosY : function( element ){

var curTop = 0;
if(element.offsetParent)
while(1){
curTop += element.offsetTop;
if(!element.offsetParent)
break;
element = element.offsetParent;
}
else if(element.y)
curTop += element.y;
return curTop;
}



Finally we have the static values for UP, DOWN, LEFT and RIGHT. Nothing mysterious here, but this technique comes in handy
for making your code more human readable. Any time you can easily replace numbers with words you should, because a wise man once said, we read our code far more often than we write it.

AnimationManager.UP = 0;
AnimationManager.RIGHT = 1;
AnimationManager.DOWN = 2;
AnimationManager.LEFT = 3;




So that's what we have so far. But what's missing? Some were mentioned at the start.


  • Move in multiple directions simultaneously

  • Move more than one element at a time



But there are a few more I can think of.

  • Pause and Play - Currently, stopping the animation and starting it again will reset the whole thing. We need to be able to pause everything, then start again from were it was.

  • Dynamically change movement instructions - If we're going to make this interactive, it can't just have a bunch of hard coded instructions that can't be changed once it gets moving.
    We need to be able to modify the instructions based on user interactions.

  • Test for collisions - Flash allows us to test whether two things on the stage are in contact with one another. We need to be able to do this with just JavaScript, HTML and CSS.


So next time we will make some changes and some additions to AnimationManager, as well as create some new wrapper and helper classes that will make the new functionality
effective, efficient and scalable.

Wednesday, February 20, 2008

The Future of Web Applications

While Mathematica is not an application most people have even heard of, let alone used, its latest incarnation is a prime example of how client-side applications can utilize more powerful machines elsewhere on the net to do the heavy lifting while still offering a user experience every bit as rich as any desktop app.

For years, Stephan Wolfram's Mathematica has been the leading desktop application for conjuring all manner of mathematical wizardry. It does everything from factoring polynomials to graphing differential equations to your freshman algebra homework. The only problem is that for more complex tasks, it can make thick black smoke spew from the ears of the average laptop. That means it won't do the college students much good unless they happen to have a kick-ass rig at their disposal. The idea behind webMathematica is to bring the desktop version's true power to those without access to the necessary hardware.

But one might ask how an application that can generate thousands of floating-point-operations per second would possibly run smoothly inside a browser. The answer turns out to be: do the actual math somewhere else.

Here's how it works. Set up your formulas, datasets and such, and when the calculations are ready to begin, all the info is sent via XMLHttpRequests to a server where all the actual math is forwarded to machines specifically designed to handle the task. The response is then rendered via JavaScript, CSS and HTML.

Very cool.

Cross Site XHR in Firefox 3

Had a look at John Resig's blog, (the author of JQuery), and he has written up the docs for the new cross-site version of the XMLHttpRequest object that will make it's debut in Firefox 3.Its actually quite simple in its implementation. The resource being accessed by the XHR simply adds an "Access-control" header specifying the domains permitted access.

Normally of course, an XHR can only make requests of the domain it came from. So if the page you are reading came from "blog.anotherreason.com", if I include a a script with an XHR, it can only make requests of that domain. Seems a pain at first, since it might be nice to grab info from an external rss feed or some such thing. But that would also mean that any scripts on the page that originate somewhere else - like an adsense script or a hit counter - could make requests of "blog.anotherreason.com", which presents problems. A malicious script could wreak havoc on my web server by making thousands of requests, (DoS attack), or could start throwing various common method names at it like deleteAccount(1) or deleteUser(1). If I am not really diligent about handling authentication on the server-side, things could go really badly. So only scripts that originate on the domain "blog.anotherreason.com" can access the other resources on that domain because one would assume that I would not write a script to mess up my own web server.

But the new cross-site XHR that will be available with Firefox 3 is a little different because I can now specify an "Access-control" header in a resource, the XHR can get at things it was previously denied by design. So if i publish an RSS feed of this blog, and put the following php code in the page that serves it up...

<? header('Access-Control: allow <*>'); ?>
<? //serve up some rss data here ?>

...anyone can create a script that updates from my RSS feed without any page reloads. Pretty cool eh?

Still wary about giving other people access to my server under any circumstances, but then again, a DoS attack can come from a bot just as easily as a script in a web page, so I will have to look into this some more.

Monday, February 18, 2008

Javascript Animation - Part 2 - Multiple Vectors

Yes, I know. "Multiple Vectors" looks very complicated and quite pretentious. But it is the most succinct way to describe what I am going to tackle in this post. If you have a better way to describe it in a couple of words, please let me know. That said, it's really not a complex concept, but it takes a bit of work to get one's mind around it, and I figure I should get your juices going early in the process.

So here's the problem.

In the last entry on the subject, I described the nature of the project - to do as much animation as possible without using anything except HTML, CSS and JavaScript. In other words, no Flash. (And certainly no SilverLight. Someday I will be including SVG in the mix, but not yet.) The first problem was getting an element to move around fast enough to appear as smooth, realistic movement, but slow enough for the eye and the brain to percieve it as motion and not an instantaneous change of location. We can do this by using JavaScript's native setInterval method to make incremental changes over time, instead of all at once. A pause of 40ms or so between executions of our moveElement function does the job. Each execution moves the element one pixel to the right, and 100 iterations later we use the JS native method clearInterval to bring things to a stop.

To check whether to continue or to stop, we simply test whether we have moved the element the desired number of pixels, and then call clearInterval...

if( current_x > dest_x )
window.clearInterval( interval_id );

Simple enough. But what if we want to now move it up 50 pixels? Your first instinct might be to simply daisy chain some setInterval calls like so...


function moveRight(){
if( current_x > dest_x ){
window.clearInterval( interval_id );
interval_id = window.setInterval( moveUp, 30 );
}else
// move the element to the right...
}


function moveUp(){
if( current_y < dest_y )
window.clearInterval( interval_id );
else
// move the element up
}

This will certainly work, but this will very quickly become very difficult to maintain. A better way to handle this would be to create a class capable of moving an element around on the page with a minimal amount of effort on the developer's part. What we're looking for here is a JS animation framework.


The AnimationManager Class


We begin with a very basic manager class to handle requests to move an element around on the page. So what do we need this class to do? First we need to give it the id attribute of an element on the page. Simple enough. Then we need to give it instructions on what we want to do with that element. Not as simple.

For the purposes of this post, we will need the class to take a series of instructions about how to move an element an arbitrary number of pixels along the X or Y axis. So here's what we would like to be able to type something like the following to move a target element in a 100x100 square, 5 pixels at a time.


var target = $( "target-div" ); // prototype element selector shortcut
var animation_mgr = new AnimationManager();
animation_mgr.addMovement( target, AnimationManager.RIGHT, 100, 5 );
animation_mgr.addMovement( target, AnimationManager.DOWN, 100, 5 );
animation_mgr.addMovement( target, AnimationManager.LEFT, 100, 5 );
animation_mgr.addMovement( target, AnimationManager.UP, 100, 5 );
animation_mgr.startAnimation();

Obviously it would be even simpler to call moveInASquareShape or something like that, and perhaps we might do that one day, but not right now. What we are looking for at the moment is some way to chain together a series of movements in an intuitive way. Here's what I came up with. Have a look, and I'll explain it all in the next post. So stay tuned!



function AnimationManager(){}
AnimationManager.prototype = {

movements : undefined,
intervals : undefined,
current_movement_index : 0,

initialize : function(){


this.movements = new Array();
this.intervals = new Array();

},


addMovement : function( element, direction, pixel_count, increment ){

if( element ){

this.movements.push( {

element : element,
start_x : 0,
start_y : 0,
direction : direction,
pixel_count : pixel_count,
increment : increment

});

}else{
// element doesn't exist
return false;
}
},

nextMovement : function(){

if( this.current_movement_index < this.movements.length ){

var movement = this.movements[this.current_movement_index];
var start_x = this.findPosX( movement.element );
var start_y = this.findPosY( movement.element );
movement.start_x = start_x;
movement.start_y = start_y;
var ani_mgr = this;

this.intervals[this.current_movement_index] = window.setInterval( function(){

if( !AnimationManager.prototype.executeMovement.apply( ani_mgr, new Array() ) ){

window.clearInterval( ani_mgr.intervals[ani_mgr.current_movement_index] );
ani_mgr.current_movement_index++;
AnimationManager.prototype.nextMovement.apply( ani_mgr, new Array() );
}

}, 30 );

return true;
}

},


executeMovement : function(){

var movement = this.movements[this.current_movement_index];
var element = movement.element;

if( element ){

switch( movement.direction ){

case AnimationManager.UP:
return this.moveUp( movement );
break;

case AnimationManager.RIGHT:
return this.moveRight( movement );
break;

case AnimationManager.DOWN:
return this.moveDown( movement );
break;

case AnimationManager.LEFT:
return this.moveLeft( movement );
break;

default: return false;
}
}else{

console.info( "no element for executeMovement" );
}


},



moveUp : function( movement ){

var destination = movement.start_y - movement.pixel_count;
var current_y = this.findPosY( movement.element );

if( movement.element && destination < current_y ){
movement.element.style.top = ( current_y - movement.increment ) + "px";
return true;
}else
return false;


},

moveRight : function( movement ){

var destination = movement.start_x + movement.pixel_count;
var current_x = this.findPosX( movement.element );

if( movement.element && current_x < destination ){
movement.element.style.left = ( current_x + movement.increment ) + "px";
return true;
}else
return false;
},


moveDown : function( movement ){

var destination = movement.start_y + movement.pixel_count;
var current_y = this.findPosY( movement.element );

if( movement.element && destination > current_y ){
movement.element.style.top = ( current_y + movement.increment ) + "px";
return true;
}else
return false;
},

moveLeft : function( movement ){

var destination = movement.start_x - movement.pixel_count;
var current_x = this.findPosX( movement.element );

if( movement.element && destination < current_x ){
movement.element.style.left = ( current_x - movement.increment ) + "px";
return true;
}else
return false;

},




startAnimation : function(){


this.current_movement_index = 0;


this.nextMovement();
},


stopAnimation : function(){

for( var i=0; i<this.intervals.length; i++ ){

window.clearInterval( this.intervals[i] );

}

},


findPosX : function( element ){

var curleft = 0;

if(element.offsetParent){
while(1){
curleft += element.offsetLeft;
if(!element.offsetParent)
break;
element = element.offsetParent;
}
}else if(element.x){
curleft += element.x;
}
return curleft;
},


findPosY : function( element ){

var curTop = 0;
if(element.offsetParent)
while(1){
curTop += element.offsetTop;
if(!element.offsetParent)
break;
element = element.offsetParent;
}
else if(element.y)
curTop += element.y;
return curTop;
}





})

AnimationManager.UP = 0;
AnimationManager.RIGHT = 1;
AnimationManager.DOWN = 2;
AnimationManager.LEFT = 3;




We can test this by placing a test element on the page, adding a few movements, and adding a couple of buttons to trigger the startAnimation() and stopAnimation() methods of the AnimationManager.

NOTE: Be sure you have the AnimationManager.js file in the proper place.



<html>
<head>
<script language='JavaScript' type='text/javascript' src='AnimationManager.js'></script>
<script language='JavaScript'>
var ani_mgr = new AnimationManager();
function onWindowLoad(){

var target = document.getElementById( "target-div" );
ani_mgr.addMovement( target, AnimationManager.RIGHT, 100, 5 );
ani_mgr.addMovement( target, AnimationManager.DOWN, 100, 5 );
ani_mgr.addMovement( target, AnimationManager.LEFT, 100, 5 );
ani_mgr.addMovement( target, AnimationManager.UP, 100, 5 );

}

</script>
<style>
#target-div{

background-color: blue;
color: white;
height: 100px;
width: 100px;
position: absolute;
top: 50px;
left: 50px;
margin: auto auto;

}
</style>
</head>
<body>
<input type="button" onclick="ani_mgr.startAnimation()" value="Start" style="display:inline"/>
<input type="button" onclick="ani_mgr.stopAnimation()" value="Stop" style="display:inline" />
<div id="target-div">Target Div</div>
</body>
</html>

Javascript Animation - Part 1 - The Simple Stuff

So I've started work on some new GUI widgets, and realized that many of them would be much easier to do in Flash. But unless I planned hook into the Flash player via fscommand or the getURL method, things would get very heavy very fast. An entirely Flash-based interface is, in my humble opinion, a waste of bandwidth and creates as many problems as it solves - not the least of which is maintainability and scalability. Rebuilding and deploying a large Flash-app is often not a trivial matter, and because the result is a binary, all or nothing file, one cannot easily turn features on and off to suit either the environment or the user's preferences. So with those concerns in mind, I wanted to see what I could do using only JavaScript, CSS and HTML, (and a little JSON and/or XML thrown in for data transfer purposes).

As soon as I started working on the main class that would handle the creation, display and animation of a few simple DIV blocks of content, I realized that I would have to recreate the kind of frame-based system that Flash uses. But since HTML and Javascript were not really intended to be an animation platform, there were a number of conceptual and practical hurdles to overcome. The one I decided to tackle first was to undermine one of the DOM's greatest strengths, and use a more obscure feature to rebuild it to my specs.


The "onpropertychange" Problem

The ability to change HTML element properties using Javascript, and have those changes be reflected instantly on the screen, is one of the most basic tasks a script can perform, and one of the strengths of the system. So overriding that behavior is neither trivial nor without consequence.


For example, if I want to animate an object's position on the screen using Javascript, I have to keep in mind that the human eye sees things at around 24 frames-per-second, meaning that anything faster will not even register. So if I try to do this...



for( var i=0; i<100; i++ )
some_element.style.left = ( findPosX( some_element ) + 1 ) + "px";


/*
NOTE: The method findPosX gets the X position of an element
regardless of the browser. I won't reproduce it here,
but you can get it at QuirksMode.org, a great site for all
things cross-browser.
*/



...the element will simply vanish and reappear instantly 100 pixels to the right . Not exactly a smooth animation. The problem is that each time the loop is executed, and the onpropertychange event is fired for the element we are moving, and the render engine moves the element to the newly specified position - in this case, one pixel to the right. But since the loop executes in a few hundredths of a second, the eye can't see what's happening during each iteration. It is in fact, worse than that, because unless the refresh rate of the screen you are using is fast enough to actually render them all, most of the iterations never even make it to your eye.

So how do we insure that things will happen at a rate that the eye can actually perceive? The answer to make sure each iteration is separated by enough time for both the screen to render the change and your eye to perceive it. That's where setInterval comes in handy.


Using setInterval to slow things down a bit

The native JavaScript method setInterval requires two arguments: a function to execute over and over again, and the number of milliseconds to wait between executions. So for example, I can declare a function that will move the element one pixel to the right...
  function moveElement(){
some_element.style.left = ( findPosX( some_element ) + 1 ) + "px";
}

...and then call that function once a second using setInterval.


  var interval_id = window.setInterval( moveElement, 1000 ); // 1000 milliseconds = 1 second  


Note how setInterval returns a unique id we can use to clear the interval using, you guessed it, clearInterval.


  window.clearInterval( interval_id );

Now the whole point is to make the element move at a rate slow enough to perceive, but fast enough to appear smooth - i.e., as though the element were a real thing moving across our field of vision, so we have to speed things up just a bit. Since 24 frames per/second is the standard for film, that's what we'll use. So if our formula is as follows:


24 frames per/second = 1 frame every 1/24th of a second 
1/24 seconds = 0.04 seconds
0.04 seconds = 40 milliseconds

So at the very minimum, we want to move the element one pixel every 40 milliseconds. But setting the interval to 40ms is, in general, not going to do the job because there are a lot of other factors at work.


Keep everything, not just the animation, moving smoothly

JavaScript is not a threaded language. Most browsers these days do create a thread for the JavaScript interpreter to execute in, but everything that happens in that thread must wait for it's turn. As a result, more than the specified number of milliseconds may pass between executions of our moveElement

method if some other JavaScript code is executed at the same time, e.g., while a user interacts with another element that uses JavaScript to change some property or another. Moreover, the browser will often pause the JavaScript thread to give priority to give some other more important thread a chance - like the one that makes HTTP requests, the one that creates context menus, etc. With this in mind, we are tempted to drop the number of milliseconds between executions down to 1, so we can be sure that the animation does not appear choppy. After all, it would seem that smooth and speedy is better than slow and choppy. But there is a downside. If we ask for the function to be executed every millisecond, we are effectively requesting the interpreter to make everything else a lower priority - including user interaction. So if by clicking the "Do Something" button, we begin our animation AND create and send an XMLHttpRequest, the request process will not have a chance to do its thing until after the animation has completed. The axiom "form follows function" holds just as true in the development of web interfaces as it does in the architecture of buildings, so we do not want to make the user wait for some important functionality just so we can be sure that we have a photorealistic animation. Better to compromise a bit and keep it at 30ms or so. That way other processes have a chance to do their thing.


So here is the code so far. Note that we check to see if the element has moved the correct number of pixels, and if it has, we stop execution. (Please ignore the ugly global variables. We'll get to making this prettier later on.)


  var interval_id; 
var some_element;
var start_position;
var end_position;

start_animation_button.onclick = function(){

// THE ABSOLUTELY POSITIONED ELEMENT WE WANT TO MOVE
some_element = document.getElementById( "some-element" );

// THE ELEMENT'S X POSITION
start_position = findPosX( some_element );

// where we want it to end up: 100 pixels to the right.
end_position = start_position + 100;

// move the element one pixel (at least) once every 30ms
interval_id = window.setInterval( moveElement, 30 );
}

// the method to execute repeatedly
function moveElement(){

// where it is at the moment
var current_left = parseInt( some_element.style.left );

// test if we've reached the destination - if so, stop executing the method
if( current_left >= end_position )
window.clearInterval( interval_id );
}



When the "start_animation_button" is clicked, we initialize a pointer to the element we want to move, and determine the start and end positions. With these three variables, we can test the current position of the element during each execution of the method, and stop execution when the element has moved the specified number of pixels to the right.


 


In the next entry, I will talk more about how to best manage multiple changes to an element during each "frame".