Subscribe to my Feed, follow me on Twitter, 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
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.

Comments
Atom feed
By Christian at Tue 10 Feb 09:14
Interesting read! In your example I guess you could also slim down the initialization and then do all the expensive stuff for a single element the first time the user tries to drag something.
Using a loader will probably make 20ms a fair wait. The upside of this is that you’ll probably never have to make everything draggable, thus saving some potential memory loss (unless you somewhere free up this.element = el)
On another point: you should use the syntax highlighting at all times, not just mouseover, makes the easier to read.
By Luke Francl at Tue 10 Feb 14:31
One of the YSlow guidelines is based on improving page load time by moving JavaScript to the bottom of the page. Browsers can’t use progressive rendering while downloading JavaScript. Also, script downloads block parallel downloads of other content. If you put the JavaScript at the bottom of your page (right before </body> if possible).
http://developer.yahoo.com/performance/rules.html#js_bottom
If you haven’t already done this, it may help, too.
By Tore Darell at Tue 10 Feb 15:10
Christian: I hadn’t thought of that, and it seems like it would work very well. The user probably isn’t going to notice those ~30ms between clicking the element and starting to drag it. It seems there are a lot of ways to optimise JavaScript performance that have to be applied wherever they make sense. I’m sometimes torn between doing things the “right” (i.e. maintainable) way and the faster way, but I guess in the end, once you get over a certain threshold, performance wins.
You’re right about the syntax highlighting too – I thought I was being clever, but it turns out it’s just annoying :)
Luke: I was actually using YUI3 on this project, which (I think) expects to be loaded right before </body>, and it does indeed help a lot. But the browser was still waiting to render the rest of the page, which I think was caused by me adding some class names to the elements. It seems that once the JS engine gets a hold of the reins it doesn’t give them up until it’s done..
By Corporate gifts at Fri 05 Mar 14:26
Use this command it really works. Using a loader will probably make 20ms a fair wait. The upside of this is that you’ll probably never have to make everything draggable, thus saving some potential memory loss (unless you somewhere free up this.element = el)
Leave a comment