Toggl Blog

Increasing perceived performance with _.throttle()

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.

Andrin Riiet

DEVELOPER
Andrin doesn’t talk much, but what he delivers works so well that there’s not actually much to talk about.
  • Show 10 Comments

    1. LS
      February 27, 2013 | Permalink

      Oh the web. A brave new frontier where dragging a coloured box around the screen bogs down a multi-gigahertz machine so much that we need to take shortcuts to make it run at an acceptable speed. Thank god I develop native code :)

    2. Francesc Rosàs
      February 27, 2013 | Permalink

      Nice, have you though to PR it to https://github.com/jquery/jquery-ui/?

      @LS, this is not a web thing. Any single threaded language or environment have to be aware of these issues.

    3. Will Stern
      February 27, 2013 | Permalink

      Great trick! I’ll have to remember that if I ever get into a massive D&D project.
      Now, if only you have a tool to increase my (perceived) job performance. j/k

      @LS Haha hilarious. It is also not an issue until you have 100 boxes and no libraries truly designed to handle that kind of load.

    4. LS
      February 28, 2013 | Permalink

      @Francesc. Single threading isn’t the problem. The problem is the processing doesn’t run at a reasonable speed.

    5. February 28, 2013 | Permalink

      @Francesc: Not at the moment. I haven’t really dug deep enough into jQuery UI’s source to know that this doesn’t break anything else (that we don’t use).

      It’s also not solving the more general problem of uncoupling UI rendering from the computational logic.
      And if we’re trying to be mobile-friendly we should use requestAnimationFrame whenever possible.

    6. simon
      February 28, 2013 | Permalink

      @LS you also have to do those kind of things on the server side or a desktop applications. Remember when computer had just on CPU and a program was making the all OS unresponsive.

      Now the browser is a platforme like an OS. When you really have a intensive work to do, you can also use https://developer.mozilla.org/en-US/docs/DOM/Using_web_workers to run your task in a real threads.

      To draw stuff (2d/3d engine) you habitually create a main loop where you set the maximum fram rate you want. If your CPU make the loop rich this time you just let the CPU sleep for a bit.

    7. Christian Bundy
      March 1, 2013 | Permalink

      Interesting, I never thought to use setTimeout() to prevent the browser from freezing – this doesn’t start another computing thread, does it?

    8. iam4x
      March 4, 2013 | Permalink

      @Christian The answer in this post will explain this well http://stackoverflow.com/questions/779379/why-is-settimeoutfn-0-sometimes-useful

      Btw, thank’s for the tip :) If you wan’t to do multi-threading in javascript using WebWorkers, try this lib : http://adambom.github.com/parallel.js/

    9. Sean May
      March 5, 2013 | Permalink

      @Christian no. It adds the function to a queue which will be processed in order.
      If you have a heavy function which you schedule, and then you use a setTimeout(func, 2), there’s no guarantee that it will run 2ms from now.
      There’s just a guarantee that it will wait at least 2ms before running.
      If there’s a lineup of stuff which is already in the queue, then it moves to the back of the line.

      @Andrin, why not take a tip from game engines and NodeJS, and decouple your manipulation/rendering functions from the browser event?

      Whether you use requestAnimationFrame (you should), or use setTimeout(…, 16), which makes a half-decent polyfill, you should probably consider setting up an update/render loop, where you poll an object for state changes.
      The browser event would then do absolutely nothing except set the state on that object.

      For something like .onmousemove, you might consider having a mouse object with x and y coordinates, and an update method which might do the jQuery job of normalizing between pageX/clientX/etc.
      All your .onmousemove is now doing is passing the event data into mouse.update, and then exiting.

      Now there’s no need to worry about calling it 100x per second.
      An event that small is going to fly by on even ancient machines.

      The next step would be to set up your update/render loop.
      A problem-specific approach might be to listen for the .onmousedown of specific elements (delegated, if plausible), and if one of those elements was clicked, start your update/draw loop, and end it .onmouseup or .ondrop, etc.

      A more-generalized solution might be to register your particular update/draw requirements into a system-wide queue/loop, which runs at 60fps (requestAnimationFrame speed), and will continue running as long as there are still items in the queue, using .onmousedown to register the intent to drag, and using .onmouseup/.onmouseleave/etc to unregister the methods from the system.

      Now, as long as your update functions aren’t ridiculous, and you aren’t doing a ton of DOM traversal, and you’ve cached your element-references, etc, you should have pretty silky and responsive movement — even on tablet/mobile, and even on crazy-intensive browser events like mousemove and scroll and resize.

    10. March 8, 2013 | Permalink

      @Sean exactly! User input should be decoupled from business logic and business logic should be decoupled from rendering logic.

      Ideally, we’d be all doing this but this is exactly what jQuery UI is not doing.

      I hope we can detach ourselves from jQuery UI in the future.
      Until then, though, this’ll have to do :]

  • Free time recording software with timesheet export.
    Check out Toggl