Skip navigation.

exploreopera

| Help

Sign up | Help

The Roost

Programming, palaver, puffins!

Posts tagged with "javascript"

PlanetWerks performance tweaks

, , ,

A great deal of work has gone into making the PlanetWerks widget run as smoothly as it possibly can. I just thought I'd share a few tweaks which have gone into the latest version (released later this week hopefully) and a few earlier versions.

Fully Matrix Driven
Up until version 2.1, the transformations performed on the orbit ellipses to orient them correctly in 3D space were done by three sets of discrete parametric equations. This simply means that once a planetoid was placed on the orbit ellipse, it was rotated once, then rotated again to incline it to the ecliptic, then rotated a third time to point the periapsis in the correct direction. That's three separate rotations before each planetoid can be displayed.

Using matrix multiplication, it is possible to combine all of these rotations into a single rotation; and since the orbit of each planetoid does not change within PlanetWerks (they do over time IRL), I can do all that preparation before the animation starts and then apply one rotation per animation frame.

Reducing Calculation Load
I spent a lot of time reviewing what actually gets executed during a single frame of animation, and trying to cut out any calculations which could be pre-calculated for speed, or algorithms which might be faster in a different form.

First of all, when I switched to the matrix driven rotation method, I had a nice function which performed the rotation from a matrix upon a vector (an [x, y, z] point on the ellipse):

Math.matrixVector = function(m, v) {
  for (var x = 0, output = []; x < m.length; x++) {
    output[x] = 0;
    for (var y = 0; y < m[0].length; y++)
      output[x] += m[x][y] * v[y];
  }
};

This slick function would handle matrices and vectors of any size, but it also has a lot of loop overhead. Since I was only ever multiplying 3x3 matrices with 3-coordinate vectors, I unrolled all the loops and calculated each term manually.

Math.matrixVector = function(m, v) {
  return [
    m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],
    m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],
    m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2]
  ];
};

This method turned out to be considerably faster, which is a good thing since this function is executed for every visible planetoid for every frame of animation in PlanetWerks!

Another thing I tried to remove was the creation of unnecessary variables. Creating a new variable using "var" can be handy, but it also entails a small overhead for both allocating new memory and garbage collection once it goes out of scope.

Since PlanetWerks 2.0, the user rotation of the system is driven by quaternions, which are simple 3 coordinate vectors with an added orientation coordinate that keeps them stable after many sequential rotations. Every time the user rotated the system, the rotation function would fire and update a global rotation matrix. When a key is held down, this rotation function can be called many times per second, so it is a good thing to optimise here. In version 2.1, the bottom section of this function looked like this:

var angle = 2 * this.math.acos(this.quat[0]), c = this.math.cos(angle), s = this.math.sin(angle), C = 1 - c;
var scale = this.math.sqrt(this.quat[1] * this.quat[1] + this.quat[2] * this.quat[2] + this.quat[3] * this.quat[3]);
var x = this.quat[1] / scale, y = this.quat[2] / scale, z = this.quat[3] / scale;

var xs = x * s, ys = y * s, zs = z * s;
var xC = x * C, yC = y * C, zC = z * C;
var xyC = x * yC, yzC = y * zC, zxC = z * xC;

this.rota = [
  x * xC + c, xyC - zs, zxC + ys,
  xyC + zs, y * yC + c, yzC - xs,
  zxC - ys, yzC + xs, z * zC + c
];
this.moved = true;

Look at all those created variables! It was surely reducing the number of duplicated calculations, but it was also causing memory allocation overhead. I did some benchmarking and found that it executed moderately faster once unrolled and calculated manually:

var angle = 2 * this.math.acos(this.quat[0]), c = this.math.cos(angle), s = this.math.sin(angle), C = 1 - c;
var scale = this.math.sqrt(this.quat[1] * this.quat[1] + this.quat[2] * this.quat[2] + this.quat[3] * this.quat[3]);
var x = this.quat[1] / scale, y = this.quat[2] / scale, z = this.quat[3] / scale;

this.rota = [
  [x * x * C + c, x * y * C - z * s, z * x * C + y * s],
  [x * y * C + z * s, y * y * C + c, y * z * C - x * s],
  [z * x * C - y * s, y * z * C + x * s, z * z * C + c]
];

More could probably be done here to reduce the number of new variables created, but readability is already getting strained to the limit.

Another thing I discovered is that cleverly combining assignments can slow execution speed by a very small amount, but still significantly. I benchmarked the following two groups of statements in Opera 9.5b2 and found that the latter, while not always the fastest, consistently executed about 5% faster than the former group:

Slower, compact, not very readable...
this.appHeight2 = (this.math.round(tbx.ratio * (this.appWidth = this.math.max(1, this.appWidth)))) / 2 + 10;
this.appWidth2 = this.math.round(this.appWidth) / 2 + 10;
this.appAbsX = this.math.abs(this.appX = this.math.atan2(tbx.x[1] / 2, tdtbxz) * this.factor);
this.appAbsY = this.math.abs(this.appY = this.math.atan2(tbx.y[1] / 2, tdtbxz) * this.factor);
this.appPosX = this.math.round(this.appX + this.width2);
this.appPosY = this.math.round(this.appY + this.height2);

Faster, longer, but also more readable...
this.appWidth = this.math.max(1, this.appWidth);
this.appHeight = this.math.round(tbx.ratio * this.appWidth);
this.appWidth = this.math.round(this.appWidth);
this.appHeight2 = this.appHeight / 2 + 10;
this.appWidth2 = this.appWidth / 2 + 10;
this.appX = this.math.atan2(tbx.x[1] / 2, tdtbxz) * this.factor;
this.appY = this.math.atan2(tbx.y[1] / 2, tdtbxz) * this.factor;
this.appAbsX = this.math.abs(this.appX);
this.appAbsY = this.math.abs(this.appY);
this.appPosX = this.math.round(this.appX + this.width2);
this.appPosY = this.math.round(this.appY + this.height2);

Binary Flags
Some functions in PlanetWerks called for systems of flags which directed the main animation function to perform in a certain way. Previously I was using a separate variable storing a Boolean for each flag. For example, there were separate flags set if the system had "moved", been "zoomed" or "rotated". In 2.2, these three flags have been combined into a single binary value with places set for each flag. 001 means the system has moved, 010 means the system has been zoomed, and 100 means the system has been rotated. Using Javascript's binary math & operator, it is easy to check whether one or multiple values have been set. If I want to know if the zoomed bit has been set, I simply "and" the set with 2:

if (this.flags & 2) { /* system has been zoomed */ }

Since 2 in binary is 010, the & operator means, if a bit is equal to 1 in both variables, set it in the result. So say the system had been rotated and zoomed: 110 & 010 = 010. And if the system had been rotated, moved, but not zoomed: 101 & 010 = 000. You can even get more complicated; suppose I wanted to know if the system had been zoomed or rotated. I would simply "and" the flag set with a combination of the zoomed and rotated flags, in other words: 011. Now if either the zoomed or rotated flag is set in the flag set, we will get a true result from the & operator.

I really don't know if this is faster than having separate variables for each, but having one entry in the lookup table instead of three has got to count for something :wink:


That's it for now. Hopefully I will get PlanetWerks 2.2 out there before I head off on holidays! :smile:

Ajax - There's nothing to it!

, , ,

These days, when I have the pleasure to meet people who at first seem as enthusiastic about web application design as I, more often than not the question eventually arises: "So, what framework are you using?" Is everyone using some kind of Ajax framework these days?

Usually I reply that I don't use any framework; I deal with Ajax on a fundamental level. It's because of this level of intimacy that I am able to code a complete Ajax exchange application from scratch without looking up anything. Well, that's not entirely true :smile: I do use a pre-coded function to create the XMLHTTPRequest object for me, mostly because of the convoluted cross-browser rules required to do so. It's everything else I can code from scratch :wink:

Now by this post I am not aiming to bash frameworks. They are perfectly fine if you want to avoid the minutia of customising a request each time you need one. My purpose here is mainly to cut away the hype-fueled framework-veneer which implies Ajax is a complicated, arcane mess of which only an API can possibly make sense. The truth is, Ajax is dead simple.

The hardest part comes first: creating the XMLHTTPRequest object. It's only hard because you need to do it a different way in Internet Explorer than the standard way in Opera and Firefox. This is the only part of the code that I need to copy and paste, because it works just the way it is and never needs to change:

/* ********************************************************
 * Modified from Jim Ley's implementation at jibbering.com
 *   http://www.jibbering.com/2002/4/httprequest.html
 *
 */
function getHTTPObject() {
  var xmlhttp = false;
  /*@cc_on
  @if (@_jscript_version >= 5)
    try {
      xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e) {
      try {
        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (e) { xmlhttp = false; }
    }
  @end @*/

  if (!xmlhttp) {
    try {
       xmlhttp = new XMLHttpRequest();
    } catch (e) {
      try {
        xmlhttp = window.createRequest();
      } catch (e) { xmlhttp = false; }
    }
  }
  return xmlhttp;
}

Examine the code if you will, but you don't need to know how it works. All you need to know is that now you can create the Ajax request object (XMLHTTPRequest or XHR) properly in virtually all modern browsers simply by calling the function.

var ajax = getHTTPObject();

So let's start with this and build on it, creating a valid Ajax application - at least the client side of things, anyway. Now that we have our XHR object, we need to give it a URI we want it to open.

var ajax = getHTTPObject();
ajax.open("GET", "/server-side-app.php", true);

The first argument of the open() method is the type of request we want to make. Usually GET is okay; it's the type of request your browser sends when you do a search on Google, for example. You'll notice you get sent to a location with a lot of variables in the address bar. This is how data gets passed using the GET method: via the URI. In our little example, we are going to assume the server-side script will send us info without us sending any special variables. We are just going to request the plain page and capture whatever output it sends back.

The second argument is the URI itself. It's relative to the document in which the script is included, and not to the javascript file if it's external.

The final argument tells the client whether or not this request should be asynchronous. Even though asynchronous is the A in Ajax, don't be alarmed at the ten-dollar terminology. It just means that the client should continue executing futher statements without waiting around for the request to finish. This keeps Ajax applications snappy, by keeping them from "freezing" while a request is taking place.

Now that we've done that, we need to put a function in place to handle the data we're going to get back:

var ajax = getHTTPObject();
ajax.open("GET", "/server-side-app.php", true);
ajax.onreadystatechange = function() {
  if (ajax.readyState == 4) {
    // Success
  }
};

During a request, an XHR object goes through several "ready state"s, finally ending up at 4, meaning the request has completed - but not necessarily successfully! Once we reach a readyState of 4, we know that there will be no futher modification to our XHR object so we can begin to extract data from it.

Now, even though the X in Ajax stands for XML, you can ignore that completely and just use the reponseText property. This returns a simple text string containing the entire reponse. For now, we'll just alert it.

var ajax = getHTTPObject();
ajax.open("GET", "/server-side-app.php", true);
ajax.onreadystatechange = function() {
  if (ajax.readyState == 4) {
    alert(ajax.responseText);
  }
};

So, we've set up the target and the response handler. All we need to do now is send it. We do this with one final statement:

var ajax = getHTTPObject();
ajax.open("GET", "/server-side-app.php", true);
ajax.onreadystatechange = function() {
  if (ajax.readyState == 4) {
    alert(ajax.responseText);
  }
};
ajax.send(null);

That's it! This script will request data from the /server-side-app.php page on your server and alert the reponse it recieves. It's up to you to take it further than this. For the most part, all you need to do is do something useful with the response, rather than just alerting it.

Another thing you'd likely do is put this code inside a function which is triggered by some user action, like clicking a checkbox, or making some other kind of selection.

One final thing to note concerns POST requests. If you encounter the situation where you need to send a great deal of data to the server, putting it all in the URI may not be feasible. Some browsers have a 1kB limit on URI sizes, and if your data is larger than that, you can kiss some of it goodbye. The solution is to use a POST request, which sends the data in a separate header which can be any length. We'll just modify our previous code like so:

var ajax = getHTTPObject();
ajax.open("POST", "/server-side-app.php", true);
ajax.onreadystatechange = function() {
  if (ajax.readyState == 4) {
    alert(ajax.responseText);
  }
};
ajax.send("myData");

So we changed "GET" to "POST" and put the data we're sending as the argument to the send() method. Easy right? Well, you'll be unpleasantly surprised when your keen-eyed swaps don't work at all. Sure, the request will get sent, but if you examine your server-side page, you'll find that it doesn't recieve any data. How odd!

The solution to this is an additional header, one which explicitly tells the remote server that there is POST data coming with the request and the server should look for it. Our code will now look like:

var ajax = getHTTPObject();
ajax.open("POST", "/server-side-app.php", true);
ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
ajax.onreadystatechange = function() {
  if (ajax.readyState == 4) {
    alert(ajax.responseText);
  }
};
ajax.send("myData");

With this header, your POST requests will now work as expected.

That's all there is to it; just build on those code seeds and you'll be forging your own Ajax applications in no time. Ajax, who needs a framework when there's just nothing to it? :D

I'm finishing this up at 3am, so if you find any errors, please let me know, eh?

Drag and Drop on a Shoestring

, , , ...

While a dynamic three-column layout might be the Holy Grail of the CSS world, cross-browser drag and drop may very well be a similar prize in Javascript. There are plenty of libraries out there designed to take into account every angle, but because they consider all cases, usually they're huge and sometimes even noticably slow.

So what if you just want to take care of the 95% case, use something totally self-contained and tiny at the same time? Well, just follow these six steps to make your elements real dragons! :smile:

Step 1 - Choose Your Element
First things first: what do you want to drag? You'll need to know how to reference it through the DOM using javascript. This is usually as easy as applying an id attribute to the element and using var elem = document.getElementById('id');. That was easy, no?

<script type="text/javascript">

window.onload = function() {
  var elem = document.getElementById('dragon');

};

</script>

...

<span id="dragon">I'm draggable!</span>

Step 2 - Apply an OnMouseDown event
To make your element draggable, all you need to apply to it is one event. It gets somewhat more complex inside the event function, but in the element context, this is all you need.

<script type="text/javascript">

window.onload = function() {
  var elem = document.getElementById('dragon');
  elem.onmousedown = function(e) {
    e = e || event;

  };
};

</script>

...

<span id="dragon">I'm draggable!</span>

e = e || event is cross-browser code so that both Opera/FF/Safari and versions of IE can all reference the event object - which is generated each time the event fires - by referencing the e variable.

Step 3 - Store Information On The Element Itself
Once the mousedown happens, a drag may or may not follow. In either case, we're going to assume one is imminent and store some useful values before we can no longer access them. First, we'll want to change the element's position CSS style to relative so we can drag it away from where it currently sits. Next, we'll want to store the current top and left styles so we know where the element will start from. (If you have previously positioned this element using right and/or bottom you'll need to use those instead). And finally, we'll want to record the position of the mouse cursor at the moment of the mousedown; we can get this information from the event object.

The trick here is that we are storing these values on the element itself. Since the element is always in context, attaching these values to the actual object we're dragging makes things quite convenient. This avoids the use of global variables which can potentially conflict with other scripts you may be using.

<script type="text/javascript">

window.onload = function() {
  var elem = document.getElementById('dragon');
  elem.onmousedown = function(e) {
    e = e || event;

    this.style.position = "relative";
    this.posX = Number(this.style.left.replace(/px/, ''));
    this.posY = Number(this.style.top.replace(/px/, ''));
    this.mouseX = e.clientX;
    this.mouseY = e.clientY;

  };
};

</script>

...

<span id="dragon">I'm draggable!</span>

Step 4 - Apply OnMouseMove and OnMouseUp Events to the Document
But aren't we moving the element? Sure we are, but we want to record all mouse movements from here on out, not just the ones which appear over the element we are dragging. For example, if you move your mouse fast enough while dragging, for some instants the cursor will be outside of the element and that event will not register with the element at all.

So instead, we'll apply the appropriate events to the document.documentElement instead. The document.documentElement is everywhere so there is no chance of us missing one of these events, unless the mouse moves entirely outside the viewport.

<script type="text/javascript">

window.onload = function() {
  var elem = document.getElementById('dragon');
  elem.onmousedown = function(e) {
    e = e || event;
    var self = this;

    this.style.position = "relative";
    this.posX = Number(this.style.left.replace(/px/, ''));
    this.posY = Number(this.style.top.replace(/px/, ''));
    this.mouseX = e.clientX;
    this.mouseY = e.clientY;

    document.documentElement.onmousemove = function(e) {
      e = e || event;

    };
    document.documentElement.onmouseup = function(e) {
      e = e || event;

      document.documentElement.onmousemove = null;
      document.documentElement.onmouseup = null;
    };
  };
};

</script>

...

<span id="dragon">I'm draggable!</span>

Two extra things to note in this addition. One is that we added two lines to the onmouseup function that apply a value of null to the onmousemove and onmouseup events of the document.documentElement. What's going on here? Well, these two lines occur as the last action of the mouseup event which cancel the functions previously set on the document.documentElement. Effectively, this just returns things to the calm and peaceful way they used to be.

The second addition is the var self = this line. We do this so that we have a variable to refer to the draggable element once we go inside the two document.documentElement functions. Once we are inside these functions, the variable this becomes a reference to the function's parent - in other words the document.documentElement itself - and it's previous meaning will be lost. By assigning the this variable to another variable (and the name self isn't special in this regard, I could have called it anything) we can then use it as a reference to the element from within the enclosed functions.

The skeleton of our drag and drop routine is now done! Nothing visual happens at the moment, but if you try dragging the element in a browser window, all the events required to perform a dragging action are firing away. Now we just have to do something visual with all those events.

Step 5 - Assume The Position
Now we're getting somewhere. All we need to do now is appropriately position the draggable element each time we recieve a new mousemove event with updated mouse cursor coordinates.

In doing this, we're going to have to get through some basic algebra, but hopefully you should understand it without any trouble. When the mousedown event was received, we recorded the position of the mouse. When we receive a mousemove event, we again receive the position of the mouse. If we subtract one value from the other (x-coord - x-coord and y-coord - y-coord) we end up with a value which tells us how far away from the starting point the mouse has moved. We use these values to adjust the original top and left styles - which we also recorded - to move our draggable element around the screen!

<script type="text/javascript">

window.onload = function() {
  var elem = document.getElementById('dragon');
  elem.onmousedown = function(e) {
    e = e || event;
    var self = this;

    this.style.position = "relative";
    this.posX = Number(this.style.left.replace(/px/, ''));
    this.posY = Number(this.style.top.replace(/px/, ''));
    this.mouseX = e.clientX;
    this.mouseY = e.clientY;

    document.documentElement.onmousemove = function(e) {
      e = e || event;

      self.style.left = (e.clientX - self.mouseX + self.posX) + "px";
      self.style.top = (e.clientY - self.mouseY + self.posY) + "px";
    };
    document.documentElement.onmouseup = function(e) {
      e = e || event;

      document.documentElement.onmousemove = null;
      document.documentElement.onmouseup = null;
    };
  };
};

</script>

...

<span id="dragon">I'm draggable!</span>

You know what? You're done! The code above works passably in Opera, Firefox, IE6 & 7, and Safari. And you thought more was required for drag and drop?

Step 6 - Customise
The code can be used as a starting point to create a full-featured drag and drop interface, implementing such things as limits (apply max and min to left and top in the onmousemove function) and/or actions to be performed on drop (execute statements within the onmouseup function). For example, here is a modification of the code above which tells you the distance you moved the element, and also returns it to its original position.

<script type="text/javascript">

window.onload = function() {
  var elem = document.getElementById('dragon');
  elem.onmousedown = function(e) {
    e = e || event;
    var self = this;

    this.style.position = "relative";
    this.posX = Number(this.style.left.replace(/px/, ''));
    this.posY = Number(this.style.top.replace(/px/, ''));
    this.mouseX = e.clientX;
    this.mouseY = e.clientY;

    document.documentElement.onmousemove = function(e) {
      e = e || event;

      self.style.left = (e.clientX - self.mouseX + self.posX) + "px";
      self.style.top = (e.clientY - self.mouseY + self.posY) + "px";
    };
    document.documentElement.onmouseup = function(e) {
      e = e || event;

      self.style.left = "0px";
      self.style.top = "0px";
      
      document.documentElement.onmousemove = null;
      document.documentElement.onmouseup = null;

      var distance = Math.round(Math.sqrt(
        Math.pow(e.clientX - self.mouseX, 2) +
        Math.pow(e.clientY - self.mouseY, 2)
      ));

      alert("You dragged the element a distance of " + 
             distance + " pixels!");
    };
  };
};

</script>

...

<span id="dragon">I'm draggable!</span>

Keep in mind that this code only applies to one particular case. If the element you're starting with is already absolutely positioned, you'll have to prepare it differently. You may need to find out the element's actual position within the document rather than relying only on recording the distance the mouse has moved. This handy function from Quirksmode.org can help with that.

If you need to know where an element was dropped, that is a whole other can of worms. The easiest situation is if your potential target elements are in fixed and absolute positions relative to the element you're dragging. So you can tell over which other element your dragged element was dropped just through some coordinate adding and subtracting. To find an arbitrary drop point, you'll have no other cross-browser recourse but to examine the page positions of all possible targets so see if they contain the coordinates of the mouse cursor during the mouseup event. Complicated, but definitely not impossible if you're determined.

Hopefully this script framework helps you get a head start on custom drag and drop functions. Good luck!

How to copy arrays and objects in Javascript

, , , ...

There are a few things that trip people up with regards to Javascript. One is the fact that assigning a boolean or string to a variable makes a copy of that value, while assigning an array or an object to a variable makes a reference to the value. The trouble this causes can range from befuddlement - when two variables you assume are separate are in fact references to the same value - to frustration when you realize there is no native way to tell the Javascript engine to pass a value rather than a reference.

In PHP, for example, all assignments make copies unless you explicitly tell the engine to pass a reference using the =& operator. If only it were so simple in Javascript.

I'm pretty much reinventing the wheel with this post since this issue has been solved before, but the reason I'm writing it is because up until recently I had always been designing custom functions to copy objects of types I designed myself. Very efficient on a per-case basis, but not very reusable. There had to be a way to make such a thing work for any and all arrays and objects. So I searched the interwebs and was enlightened.

Firstly, arrays. Surprisingly, arrays are easy to copy because a couple of native Array object methods actually return a copy of the array. The easiest to use is the slice() method:

var foo = [1, 2, 3];
var bar = foo;
bar[1] = 5;
alert(foo[1]);
// alerts 5

var foo = [1, 2, 3];
var bar = foo.slice(0);
bar[1] = 5;
alert(foo[1]);
// alerts 2

The slice(0) method means, return a slice of the array from element 0 to the end. In other words, the entire array. Voila, a copy of the array. The only caveat to remember here is that this method works if the array contains only simple data types, like numbers, strings and booleans. If the array contains objects or other arrays (a multi-dimensional array), then those contained "objects" will be copied by reference, retaining a connection with the source array. In such a case you will need to copy the array as a full-fledged object.

Objects are trickier because there is no native method which returns a copy of the object. So instead we add one ourselves using a prototype method:

Object.prototype.clone = function() {
  var newObj = (this instanceof Array) ? [] : {};
  for (i in this) {
    if (i == 'clone') continue;
    if (this[i] && typeof this[i] == "object") {
      newObj[i] = this[i].clone();
    } else newObj[i] = this[i]
  } return newObj;
};

Notice the recursion going on; isn't it divine? :smile: The clone() method steps through the properties of any object one by one. If the property is itself an object or array, it calls the clone() method on that object too. If the property is anything else, it just takes the value of the property. Then the result received is assigned to a property of the same name in a new object.

Finally, after we're done stepping through all properties, return the new object which is a copy of - not a reference to - the old object. A call to the clone method is as simple as this:

var foo = {a: 1, b: 2, c: 3};
var bar = foo;
bar.b = 5;
alert(foo.b);
// alerts 5

var foo = {a: 1, b: 2, c: 3};
var bar = foo.clone();
bar.b = 5;
alert(foo.b);
// alerts 2
July 2008
SMTWTFS
June 2008August 2008
12345
6789101112
13141516171819
20212223242526
2728293031