This is a technical post by our front-end developer Andrin. He has been involved with another product of ours – Teamweek – for a while.

In web applications, page responsiveness is a key component of a good user experience. Dealing with an app that is slow and frequently hangs can be an extremely frustrating experience.

If you’re dealing with large amounts of data or doing complex calculations in javascript, you probably know that, due to javascript’s single-threaded nature, it’s wise not to do too much work at once.

e.g. you don’t want to do this

for(var i = 0; i < 10000000; i++) {
  calculate_something();
}

But if you’ve got 10000000 pieces of data to process, your only options are to user web workers or split the loop like this:

for(var i = 0; i < 1000; i++) {
  setTimeout(function() {
    for(var j = 0; j < 10000; j++) {
      calculate_something();
    }
  }, 0);
}

The setTimeout(..., 0) trick here gives the browser a chance to breathe before moving on to the next batch, thus preventing the browser from freezing.

We recently tackled a similar problem to the one described above. The Teamweek app makes use of pointer gestures like drag&drop to be as intuitive as possible and we rely heavily on jQuery UI to be able to do that.

But with large amounts of draggables and droppables on the page, it’s only a matter of time before the browser gets bogged down by having to process too many interactive elements.

Here’s where underscore’s _.throttle() comes in.

From underscore’s documentation:

_.throttle(function, wait)
Creates and returns a new, throttled version of the passed function, that, when invoked repeatedly, will only actually call the original function at most once per every wait milliseconds. Useful for rate-limiting events that occur faster than you can keep up with.

The problem we have is that so much processing is going on upon each mouse move event, that the browser doesn’t even get the chance to rerender the draggable at its new position. This is mostly noticeable in Firefox.

Here we can do some monkey patching (or duck punching if you prefer) to slip in a throttled version of the mousemove event handler, giving the browser a chance to do a repaint.

The function we want to throttle is jQuery.ui.mouse._mouseMove.

In theory it’s as simple as

jQuery.ui.mouse.prototype._mouseMove = _.throttle(jQuery.ui.mouse.prototype._mouseMove, 20);

The 20 milliseconds translates to 50 fps.

But because of the way _.throttle works, we have to take extra care to make sure that _mouseMove doesn’t get called after _mouseUp.

We can simply patch these as well so we end up with:

var drag_active = false;

var original_mouseMove = jQuery.ui.mouse.prototype._mouseMove;
jQuery.ui.mouse.prototype._mouseMove = function() {
  if(drag_active) {
    original_mouseMove.apply(this, arguments);
  }
}

var original_mouseDown = jQuery.ui.mouse.prototype._mouseDown;
jQuery.ui.mouse.prototype._mouseDown = function() {
  drag_active = true;
  original_mouseDown.apply(this, arguments);
}
var original_mouseUp = jQuery.ui.mouse.prototype._mouseUp;
jQuery.ui.mouse.prototype._mouseUp = function() {
  original_mouseUp.apply(this, arguments);
  drag_active = false;
}

jQuery.ui.mouse.prototype._mouseMove = _.throttle(jQuery.ui.mouse.prototype._mouseMove, 20);

It’s not pretty but it works. The dragging is way smoother with this hack in place. Alternatively we could go straight to the jQuery UI source and modify the way dragging is handled, or do away with jQuery UI completely which is a huge amount of work. For now, this works well enough.

Here we have a situation where, by doing fewer computations per second, we can significantly increase the application’s (perceived) performance.

Sometimes less really is more.

Teamweek project planning tool
TeamWeek is an online Project Planning Tool with a Team CalendarBeing an antidote to clumsy Gantt charts, it allows managers to respond to change faster.

FacebookTwitterGoogle+