Sometimes you may think you are doing a nice simple effect in jQuery. In reality, you are making hundreds of unnecessary DOM calls and updates every second. And you wonder why the effect is taking so long to run.

This post hopes to teach you how to optimise your code to run that little bit quicker.



Let us start with a scenario. You have a category page with a list of products and in the sidebar a list of additional attributes. You want, when you hover over the products, for jQuery to add the class ‘active’ to the attributes and for every other product to fade a little

Here is a bit of code we will start with:

[html]

[/html]

This HTML is a list item that has an image to the product, and some hidden spans with the ids of the attributes to target.

[javascript]

$(‘ul.products-grid li.item’).hover(

function(){

$(‘ul.products-grid li.item’).not($(this)).stop().animate({‘opacity’:’0.3′}, 1000);

var stones = $(this).find(‘.shapes’).html().split(‘,’);

for (index in stones)

{

$(‘.product-highlights .stones li[data-shapeid=”‘+stones[index]+'”]’).addClass(‘active’);

}

var metals = $(this).find(‘.metals’).html().split(‘,’);

for (index in metals)

{

$(‘.product-highlights .metals li[data-metal=”‘+metals[index]+'”]’).addClass(‘active’);

}

},

//When hover off, remove class

function(){

$(‘ul.products-grid li.item’).stop().animate({‘opacity’:’1′}, 1000);

$(‘.product-highlights .stones li,.product-highlights .metals li’).removeClass(‘active’);

}

);

[/javascript]

Now this seems quite simple. When we hover on a product, for every list item that is not being hovered, fade; grab the ids of the attributes, find the attributes and add the class ‘active’ to them. When we hover off a product, make all products fully visible and remove all the active classes.

There are a couple of problems with this code. To begin with, the number of DOM look-ups we need. We are searching for all list items every time we do a hover. We can move that look-up to outside our hover function so that it is done only once:

[javascript]

var listItem = $(‘ul.products-grid li.item’);

[/javascript]

Now we can make every list item that is not being hovered faded:

[javascript]

listItem.not($(this)).stop().animate({‘opacity’:’0.3′}, 1000);

[/javascript]

This is still doing a lot of DOM manipulation, but no more DOM look-up.

Then we get to the first of our loops – look for all stones and add the active class. jQuery allows us to target multiple elements using multiple selectors in one statement. So let us put all those selectors into one selector so jQuery only needs to be called once to do the look-ups, and once to do the DOM manipulation:

[javascript]

//get ids of attributes to target

var stones = $(this).find(‘.diamond-shapes’).text().split(‘,’);

var selectors = []; //elements to add active class to

//for each id, add the selector to the array

for (index in stones)

{

selectors.push(‘.product-highlights .stones li[data-shapeid=”‘+stones[index]+'”]’);

}

//add active class to all elements.

$(selectors.join(‘,’)).addClass(‘active’);

[/javascript]

What we are doing is adding the selector to an array, then joining the selectors in a single jQuery look-up, and adding the class ‘active’ to the targeted elements.

We now extend that to include the metal attributes:

[javascript]

var selectors = [];

var stones = $(this).find(‘.diamond-shapes’).text().split(‘,’);

for (index in stones)

{

selectors.push(‘.product-highlights .stones li[data-shapeid=”‘+stones[index]+'”]’);

}

var metals = $(this).find(‘.metals’).html().split(‘,’);

for (index in metals)

{

selectors.push(‘.product-highlights .metals li[data-metal=”‘+metals[index]+'”]’);

}

$(selectors.join(‘,’)).addClass(‘active’);

[/javascript]

Our hover animations are now running super sweet fast! But there is a little lag when we hover off our products.

Outside our hover function, where we declared listItem, we also declare a new variable named stonesAndMetals. This will target all of the highlight attributes:

[javascript]

var stonesAndMetals = $(‘.product-highlights li’);

[/javascript]

Now, in our hover-off function, we target all the list items and highlight attributes using the previously declared definitions.

[javascript]

listItem.stop().animate({‘opacity’:’1′}, 1000);

stonesAndMetals.removeClass(‘active’);

[/javascript]

Each of these optimisations reduce the number of DOM look-ups required, greatly reducing the time required to start and finish each function.

[Thanks to Ross for pointing this one out]

It is also better to store $(this) in a local variable if it is being called more than once. This will, again, save the DOM lookups for the specific element.

Here is the final, optimised code:

[javascript]

var listItem = $(‘ul.products-grid li.item’);

var stonesAndMetals = $(‘.product-highlights li’);

$(‘ul.products-grid li.item’).hover(

//on hover

function(){

var item = $(this);

//fade other items out

listItem.not(item).stop().animate({‘opacity’:’0.3′}, 1000);

var selectors = []; //elements to add active class to

//for each id, add the selector to the array

var stones = item.find(‘.shapes’).text().split(‘,’);

for (index in stones)

{

selectors.push(‘.product-highlights .stones li[data-shapeid=”‘+stones[index]+'”]’);

}

//do the same with the metals.

var metals = item.find(‘.metals’).html().split(‘,’);

for (index in metals)

{

selectors.push(‘.product-highlights .metals li[data-metal=”‘+metals[index]+'”]’);

}

//add active class to all elements.

$(selectors.join(‘,’)).addClass(‘active’);

},

//Hover off

function(){

//remove highlight and slide up the ring details

listItem.stop().animate({‘opacity’:’1′}, 1000);

stonesAndMetals.removeClass(‘active’);

}

);

[/javascript]

Comments