Sneaky Abstractions

Subscribe to my Feed, follow me on , recommend me on Working With Rails or see my code on GitHub

Optimising by cheating - how to trick your users into thinking your site is fast

Posted on February 10, 2009 08:03 Tagged with javascript, optimisation, magic.

Optimising web site performance is somewhat of a black art – bottlenecks and slowdowns are sometimes found in unlikely places. Usually, most of the time from a user clicks a link until he sees the page in the browser is spent rendering the page and running JavaScripts that add behaviour to the elements. The ratio is usually somewhere around the familiar 80/20 split, so while optimising server performance is all well and good, it’s not going to have a huge impact compared to improving its performance in the browser. So you profile and debug your HTML, CSS and JavaScript to find the bottlenecks and make them faster. But when you’ve squeezed every last millisecond out of the browser’s rendering and JavaScript engine and it’s still slow, what do you do? Well, you cheat.

Do it on the off-beat

Your users don’t care about the time it takes for your JavaScript to run, they care about perceived performance. One way to cheat is to make it appear like the page is fully loaded while your JavaScript continues to run in the background.

When performing tricks, magicians will create a distraction so they can mark a card or hide something while you’re not paying attention – they’re doing it on the off-beat. Your users will usually spend a couple of seconds scanning the page after it loads to see what’s on it – this is the off-beat where you can work your magic while the user is distracted.

The setup

So, what’s the secret to performing this magic trick well? First of all you have to make sure the user can’t see what you’re doing. Whatever you’re doing to the page must have minimal visual impact, or else the user will notice your distraction and the trick won’t work. This means not all of your JavaScript can be deferred to the off-beat, but most of the time you will have found some expensive parts that can by profiling the page. The page, when displayed to the user, must be usable in its initial state. Writing your JavaScript unobtrusively helps a long way in achieving this.

The example

I’ll use an example I ran into recently where making an element draggable took between 30-50ms. That doesn’t sound like much, but when you have 50 or more elements on the page that need to be draggable that means the user will have to wait an extra two seconds until the page is ready. This is many times the normal time it takes for a request to be returned from the server and makes a big difference in how fast the user perceives the page to be.

The code looked something like this:

Mysite = {};

Mysite.Thing = Class.create({

  initialize: function(el){
    this.element = el;
    this.makeDraggable();
  },

  makeDraggable: function(){
    //Make the element draggable
  }

});

onDOMReady(function(){

  //Find all things and add them to Mysite.things
  Mysite.things = $$('.thing').map(function(el){
    return new Mysite.Thing(el);
  });

});

This worked fine, but the problem was the makeDraggable method which was being run on initialisation of each new Thing. The browser would stop rendering while doing this, causing the page to appear as if it was still loading. And indeed, it was still loading – all the user would see was a spinning cursor of some sort and a blank canvas, even though the document had already loaded and was ready to be displayed. So how could this be deferred until after the page has been rendered?

The magic wand: setTimeout

The problem with running a lot of JavaScript on page load is that while running it the browser will stop rendering until you hand control back to it. If you can do this, the browser can finish rendering the page and then you can run time-intensive code. To hand control back to the browser, you can use the setTimeout function:

Mysite.Thing = Class.create({

  initialize: function(el){
    this.element = el;
  },

  makeDraggable: function(){
    //Make the element draggable
  }

});

onDOMReady(function(){

  //Find all things and add them to Mysite.things
  Mysite.things = $$('.thing').map(function(el){
    return new Mysite.Thing(el);
  });

  //Make all things draggable
  setTimeout(function(){
    Mysite.things.each(function(thing){
      thing.makeDraggable();
    });
  }, 1000);

});

Notice that the call to makeDraggable has been moved out of the initialize method and into an anonymous function in setTimeout. As soon as the timeout is set, the browser considers this bit of code finished and goes on to render the page. Then, when the timeout expires, the elements are made draggable. Usually the user won’t notice a thing because he’s distracted trying to make sense of the page that has just been loaded, and when he starts interacting with it it’s as if nothing happened.

I’m Sheriff John Bunnell, when you hear police sirens it means one of two things; someone is in trouble or someone is causing trouble. In the next hour, we’re gonna show you trouble, up close, the way the police see it every single day. So fasten your seatbelts, we’re gonna take you for a wild ride.