Optimizing JavaScript Performance: Handling Coordinate Updates

Handling Coordinate Updates

In a recent JavaScript project, I needed to listen for keyboard events to move an object around (think Pong or PacMan). During initial development, I noticed that the responsiveness of key action to object movement was severely lacking. Upon investigation, I realized that I was setting my object coordinates in the wrong place, resulting in what seemed to be a much lower frame rate, despite using requestAnimationFrame for animation.

That being said, here’s a quick rundown of what to watch out for if you encounter a similar issue, and how to correctly handle coordinate updating in your JavaScript projects.

The Problem: Updating Coordinates in the “keydown” Event Handler

Here are a few snippets of my code. At first glance, everything looks to be OK. But it really isn’t…

var rAF = (function(){
  return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60); };
})();
function update() {
  rAF(update);
  render();
}
function render() {
  // Draw methods for canvas go here...
}
function onKeyAction(e) {
  // Note: keydown is an object with Boolean values for left/right/up/down,
  //       box is an object with numeric values for x/y/width/height
  switch (e.keyCode) {
    case 37:
      keydown.left = (e.type === "keydown");
      box.x = box.x - ((e.type === "keydown") ? 5 : 0);
      break;
    case 38:
      keydown.up = (e.type === "keydown");
      box.y = box.y - ((e.type === "keydown") ? 5 : 0);
      break;
    case 39:
      keydown.right = (e.type === "keydown");
      box.x = box.x + ((e.type === "keydown") ? 5 : 0);
      break;
    case 40:
      keydown.down = (e.type === "keydown");
      box.y = box.y + ((e.type === "keydown") ? 5 : 0);
      break;
  }
}
document.addEventListener("keydown", onKeyAction);
document.addEventListener("keyup", onKeyAction);
rAF(update);

When I tested the project with the code above, I noticed that my box moved very sluggishly. Doing some debug on it, I calculated that, despite using the aforementioned requestAnimationFrame for animating, the box moved at approximately 12 frames per second (despite the overall animation frame rate being around 60fps).

The Solution: Updating Coordinates in the Callback Method of requestAnimationFrame

Rather than calculating the x/y coordinates of my box right inside of the key event handler, I moved the logic into my update function, which is my callback method of requestAnimationFrame. Here’s what the updated code looks like:

var rAF = (function(){
  return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60); };
})();
function update() {
  if (keydown.left) {
    box.x -= 5;
  }
  if (keydown.right) {
    box.x += 5;
  }
  if (keydown.up) {
    box.y -= 5;
  }
  if (keydown.down) {
    box.y += 5;
  }
  rAF(update);
  render();
}
function render() {
  // Draw methods for canvas go here...
}
function onKeyAction(e) {
  // Note: keydown is an object with Boolean values for left/right/up/down,
  // box is an object with numeric values for x/y/width/height
  switch (e.keyCode) {
    case 37:
      keydown.left = (e.type === "keydown");
      break;
    case 38:
      keydown.up = (e.type === "keydown");
      break;
    case 39:
      keydown.right = (e.type === "keydown");
      break;
    case 40:
      keydown.down = (e.type === "keydown");
      break;
  }
}
document.addEventListener("keydown", onKeyAction);
document.addEventListener("keyup", onKeyAction);
rAF(update);

Note that in the updated code, the box object is not updated directly in the onKeyAction method, but is set in the update method based on which keys are being pressed. Since the calculations of the coordinates are in the update method, the box moves much more smoothly, at the pure frame rate (60fps) rather than at a janky frame rate determined by the frequency of the onKeyAction method being called.

The Proof: A Simple Demo Project

At first glance, the code snippets and explanations above may not seem to be entirely clear. So, for some proof of how much of a difference there really is between the problem and the solution, I put together a little demo. Check it out for yourself, and toggle between the two methods of coordinate setting. I think you’ll be amazed at how vast the difference really is.

Note: The demo requires a keyboard input with arrow keys.

View Demo »

Wrapping Up

The big takeaway from this is to ensure your JavaScript code is set up intelligently, and that if you encounter any jankiness in the performance of your project, make sure that you aren’t performing calculations in the wrong place. As you saw here, small changes in your code can make a huge difference in performance.

Until next time, happy coding!

Author’s Note

Since publishing this article, I’ve had some great feedback regarding some of the coding logic and terminology used.

First, it’s been pointed out that the demo clears the entire canvas rather than just clearing the area where the box used to be. I completely agree that the demo could be set up to only clear the necessary area, which would help improve overall performance, especially on larger-scale projects and canvases.

Also, it’s been noted that the article’s title and point are a bit misleading, since the problem and solution described are actually issues with where the calculations are placed, not with the overall performance. I agree that the problem and solution described aren’t necessarily true performance issues. My intent was not to infer that the location of calculation placement directly affected the overall performance. Rather, my intent was to show the difference in the responsiveness of the two demos as the result of correctly placed calculations. As is mentioned in the first paragraph, “…I noticed that the responsiveness of key action to object movement was severely lacking. Upon investigation, I realized that I was setting my object coordinates in the wrong place, resulting in what seemed to be a much lower frame rate…”

That said, in my mind, a lag in responsiveness is a still performance issue. It may not actually make the JavaScript run any less efficiently, but it still adversely affects the overall experience of the demo, which was what I intended to show here.

3 Responses to this post:

  • Thomas Giles says:

    An interesting article. It seems, however, to be erroneous in its conclusions. Let me explain…

    Go into some text editor or what-have-you and click to place your cursor in the text. Now watch your cursor while holding down an arrow key. It moves one space immediately, waits a moment, and then moves at (probably, judging from what was said in the article) 12fps, one character per frame. This has nothing to do with performance.

    Usually, the OS has a setting for “character repeat delay” and “rate”. These determine this kind of “rapid-fire” behaviour, and carried through to the “keydown” event triggering. In the original code, the box’s position was being updated on every one of those input events, which gives the same appearance as the cursor moving along some text.

    So the appearance of jankiness is not jankiness at all. It has nothing to do with performance.

    In the “fixed” version, he only captures which keys are down or up. Then for the entire duration of a key being down, its affect is applied to the box object. And when the key is released (“onkeyup”), that affect is no longer applied, and so the box stops moving.

    Again, this has nothing to do with performance, but was basically a bug in the original code that happened to be fixed with the changes he made. And if you think about it from the other side, he’s updating a property’s value! It would be very unlikely for that to cause performance issues! ;P

    An interesting problem to look at either way, but thought I’d just clear up some possibly misleading conclusions discussed in the article.

  • Alexey Raspopov says:

    > (e.type === “keydown”) ? true : false;

    Your expression returns boolean value, so you can avoid using ternary operator

  • Thanks for the comments, both here and on some of the various social networks. I have updated the article with an “Authors Note” at the end in response to the comments I have received about the issue of performance vs responsiveness. My apologies if the use of terminology was a bit loose in the original article, as this was not my intention.

    Also, good catch about the unnecessary ternary operators, @Alexey. I have updated the code to remove that unnecessary logic.

    Again, thanks for the great comments and feedback. I enjoy gathering this kind of feedback, as it not only helps me write better articles, but ultimately also helps me become a better developer. Cheers!

Leave a Comment