Implementing Movable Objects with JavaScript

By John Slater

It is easy to use JavaScript to move existing DOM objects, and there are abundant examples and tutorials on the web. Building movable objects while your program is running, and giving these objects behaviors is a more difficult task. This article discusses one way of doing this. The code is simple, but anonymous functions are an essential ingredient and there are some aspects of function definition that you need to understand. These aspects will be explained. Finally, you will see how JSON (JavaScript Object Notation) can be used much like CSS to make the process of style definition more powerful and less verbose.

This article will build incrementally towards the final code, so you can see the rational for how the final code has been designed.

Create Objects that Correspond to your DOM Nodes

Lets start with a trivial example of 2 DOM objects that are moved using the JavaScript setInterval function:

<head>
<script type="text/javascript">
var left1=100;
function movePink() { left1+=  2; document.getElementById('pink').style.left=left1; }
var left2=300;
function moveBlue() { left2+= -2; document.getElementById('blue').style.left=left2; }
window.onload=function() {				
  window.setInterval(movePink,50);
  window.setInterval(moveBlue,50);
}
</script>
</head>
<body id="bodyID">
<div id="pink" style="position:absolute;top:100px;left:100px;width:50px;height:50px;background:pink">
pink block
</div>
<div id="blue" style="position:absolute;top:200px;left:300px;width:50px;height:50px;background:blue">
blue block
</div>
</body>
demo

We really should create JavaScript objects that correspond to each of the DOM objects we want to move. However, this does NOT work:

function Block(id,x,dx) {
  this.id=id;
  this.x = document.getElementById(id).style.left;
  this.dx=dx;
  this.move=function() { 
    this.x+=this.dx;
    document.getElementById(this.id).style.left=this.x;
  }
}
var pinkBlock,blueBlock;
window.onload=function() {
  pinkBlock=new Block('pink',100, 2);
  blueBlock=new Block('blue',300,-2);
  window.setInterval(pinkBlock.move,50); // ***does NOT work***
  window.setInterval(blueBlock.move,50); // ***does NOT work***
}
demo - does not work, error occurs

setInterval is passed a pointer to pinkBlock.move. After the specified delay, setInterval executes the function located at the pointer pinkBlock.move. So pinkBlock.move is called as a function, not a method. When pinkBlock.move is called as a function, this does not refer to a Block instance, and this.id, this.x, and this.dx are undefined.

Replacing

  window.setInterval(pinkBlock.move,50); 
  window.setInterval(blueBlock.move,50); 
with
  window.setInterval(function() { pinkBlock.move(); },50);
  window.setInterval(function() { blueBlock.move(); },50);
solves our problem. window.setInterval is given a pointer to an anonymous function. The anonymous function contains the method call blueBlock.move().

demo - is now works!

Wrapping a method call in an anonymous function is a useful trick you will want to remember.

Make Objects and their Motions Separate Classes

A block may not move all the time. Some blocks may never need to be moved. But currently all our blocks have the property dx. So it might be better if we had a movement object that could be applied to blocks or other objects.

Lets create a movement class, which we will call Translate. Once you have this class, it is easy to add the capability to start and stop movement. At this point it makes sense to generalize our movement, so x and y motions are included in this code:

function Block(id,x,y) {
  this.id=id;
  this.x = x;
  this.y = y;
}
function Translate(obj,dx,dy) {
  this.obj=obj;
  this.dx=dx;
  this.dy=dy;
  this.move=function() {
    this.obj.x+=this.dx;
	this.obj.y+=this.dy;
    document.getElementById(this.obj.id).style.left=this.obj.x;
	document.getElementById(this.obj.id).style.top=this.obj.y;
  } 
  var self=this;
  this.start=function() { this.pid=window.setInterval(function() { self.move(); },50); }
  this.stop =function() { clearInterval(this.pid); }
}
var pinkBlock,blueBlock;
window.onload=function() {
  pinkBlock=new Block('pink',100,100);
  blueBlock=new Block('blue',300,200);
  pink_goRight=new Translate(pinkBlock, 2,0);
  blue_goLeft=new Translate(blueBlock,-2,0);
  window.setTimeout(function() { pink_goRight.start();},1000);
  window.setTimeout(function() { blue_goLeft.start(); },2000);
  window.setTimeout(function() { pink_goRight.stop(); },4000);
}
demo (same as the previous demo, but the implementation has been modified)

While this code looks straightforward, there are some subtleties that need to be discussed.

This version of start does not work:

function Translate(obj,dx,dy) {
..
//var self=this;
//this.start=function() { this.pid=window.setInterval(function() { self.move(); },50); }
  this.start=function() { this.pid=window.setInterval(function() { this.move(); },50); } // won't work!

Functions defined within the context of a parent function know about the local variables of the parent function. So the anonymous functions above know about the local variables within Translate. But this is not a local variable, it is more like an operator. We get around this problem with var self=this, which uses this to get a pointer the object, and set the pointer value to the local variable self.

You can get tripped up by another fine point when you start doing your own programming. There is almost never any difference operationally between

function ObjectFoo() {
  this.aMethod=function() { .. }
}
and
function ObjectFoo() {
}
ObjectFoo.prototype.aMethod=function() { .. }

The first approach creates the method aMethod every time ObjectFoo is used as a constructor. If you make a lot of ObjectFoo objects, you will be creating a lot of copies of aMethod. Each of these methods has as a context an instance of ObjectFoo; but usually this is of no importance.

The second approach creates a single instance of the aMethod and associates aMethod with the class prototype, ObjectFoo.prototype. This is usually the preferred way of doing things. However, this single instance of aMethod is not created within the context of an instance of ObjectFoo, and does not know about local variables of the constructor.

This test case demonstrates the problem:

function ObjectFoo() {
  var aLocalVariable='abc';
  this.aMethod1=function() { alert(aLocalVariable) }
}
ObjectFoo.prototype.aMethod2=function() { alert(aLocalVariable) }

window.onload=function() {				
 (new ObjectFoo()).aMethod1(); // 'abc'
 (new ObjectFoo()).aMethod2(); // undefined
}

One has less worries with Ruby, which has method objects, and aMethod#call runs the method just as if you had invoked the method via its object.

Have your Object Create the DOM Node During Instantiation

There is a one to one correspondence between a DOM object we want to move, and the JavaScript object we create in order to control the DOM object's movement. We automatically maintain this correspondence if we create the DOM object when we call the Block constructor.

It is helpful to write some convenience functions to get a DOM element and create a new one:

function elm(id) { return document.getElementById(id); }
function NElm(tagName,name,parent) { 
  var elm=document.createElement(tagName); 
  if(name  ) { elm.setAttribute("id",name); }
  if(parent) { parent.appendChild(elm);     }
  return elm; 
}

Our Block function now looks like:

function Block(id,x,y,dx,dy) {
  this.id=id;
  this.x = x;
  this.y = y;
  this.dx=dx;
  this.dy=dy;
  this.DOMnode=NElm('div',id,elm('bodyID'));
  this.DOMnode.style.position="absolute";
  this.DOMnode.style.left=x+"px";
  this.DOMnode.style.top =y+"px";
  this.DOMnode.style.width ="50px";
  this.DOMnode.style.height="50px";
  this.DOMnode.style.background=id; // we assume the id is also the color (BAD!)
}
demo (same as the previous demo, but the implementation has been modified)

It takes one line to assign each of the style properties. We have only width, height, and background to deal with in this example. But in general there might be dozens of style properties, and it might be nice if they were not embedded in the code.

Use JSON to Create Your Own Style Sheets

Style sheets have a couple of well known shortcomings: they are not XML and they are static, are best you can switch style sheets. So if you are using JavaScript to manipulate your web page, it might be better to implement your own alternative to style sheets. This is very easily done.

First you need a merge function, which takes a list of objects and adds each property of each object to a return object, over writing any prior property value of the return object. The return object is named allProperties in this code:

function mergeProperties(listOfObjects) {
  var allProperties={};
  for(var i=0; i<listOfObjects.length; i++) {
    var object=listOfObjects[i];
    for(var property in object) { allProperties[property]=object[property]; }
  }
  return allProperties;
}

Next, make use of JavaScript Object Notation (JSON), to define the style of your block. This definition, block_style, is JavaScript code and could be contained in a separate .js file which gets included in various web pages, much like you load style sheets into web pages.

var block_style={position:'absolute',width:'50px',height:'50px',background:'silver'};

function Block(id,x,y,dx,dy,style) {
  this.id=id;
  this.x = x;
  this.y = y;
  this.dx=dx;
  this.dy=dy;
  this.style=style;
  this.DOMnode=NElm('div',id,elm('bodyID'));
  this.DOMnode.style.left=x+"px";
  this.DOMnode.style.top =y+"px";
  for(var i in this.style) { this.DOMnode.style[i]=this.style[i]; }  
}

Note, we do not have to subclass Block to change its size or default background color, since these properties are part of the Block's style argument.

Attach Behaviors to Objects

A circular motion might be associated with an insect flying near a light, or a Roomba vacuum when it is cleaning in the middle of a room. And hungry animals may chase or seek their prey. Simple Circle and Seek behaviors can be added, and are very similar to the existing Translate motion. The start and stop methods turn these behaviors on and off.

For demonstration purposes and onload has been modified to use these behaviors. Can you think of some simple behaviors that might be interesting to add?

window.onload=function() {
  pinkBlock=new Block('pink',100,100,mergeProperties([block_style,{background:'pink'}]);
  blueBlock=new Block('blue',300,200,block_style);
  
  var pink_goRight=new Translate(pinkBlock, 2,0);
  var blue_goLeft=new Translate(blueBlock,-4,1);
  var pink_circling=new Circle(pinkBlock,40);
  var blue_seeking=new Seek(blueBlock,pinkBlock);

  window.setTimeout(function() { pink_goRight.start(); },1000);
  window.setTimeout(function() { blue_goLeft.start();  },2000);
  window.setTimeout(function() { pink_goRight.stop();  },4000);
  window.setTimeout(function() { pink_circling.start();},4500);
  window.setTimeout(function() { blue_goLeft.stop();   },6000);
  window.setTimeout(function() { blue_seeking.start(); },7000);
}
demo, objects with behaviors, uses JSON to specify style

As an exercise, you can create buttons which allow the user to add additional objects and apply various movements or behaviors to them.

The code in onload is starting to get unwieldy. But designing functions that clean this up really belongs in another article.

Conclusion

This article shows how to build movable objects dynamically (i.e. while your program is running) and give these objects movements or behaviors. Since JavaScript only has C-style pointers to methods, the setInterval function is given anonymous functions which contain whatever object's method we need to call.

CSS properties are specified using JavaScript Object Notation (JSON), and applied using a very simple utility function.

If you look at the source code of the last demo, you will see how simple it is. The final JavaScript program consists of

It is easy to add new objects and behaviors. Hopefully you will find interesting ways of using it with your web pages.

This code was tested with IE7 and FF2.0 on a Windows XP computer. Let me know how it works on your machine.

About the Author

John Slater is a programmer living in Silicon Valley. When he is not JavaScripting he is probably out biking somewhere.