NudeCanalTroll
NudeCanalTroll

Reputation: 2286

Speed up jQuery manipulation of a list

My application has a long list of objects whose width needs to be modified via jQuery. Currently I'm using code like this:

$('#my_list div.text_field').each(function() {
  // Cache objects we're going to use multiple times.
  var $textField = $(this);
  var $listContents = $textField.closest('div.list_contents');

  // Find widths.
  var contentsWidth = $listContents.outerWidth();
  var avatarsWidth = $listContents.find('div.avatars').outerWidth();
  var tagsWidth = $listContents.find('div.tags').outerWidth();
  var textLeft = $textField.position().left;

  // Update the width.
  var newTextWidth = contentsWidth - textLeft - avatarsWidth - tagsWidth;
  $textField.css({ width: newTextWidth });
});

However, it takes a while (> 1 second) when there are hundreds of objects to be manipulated. Any idea how I could make this faster? Should I totally eschew jQuery and use native JS?

Upvotes: 1

Views: 385

Answers (2)

NudeCanalTroll
NudeCanalTroll

Reputation: 2286

Okay, through a series of improvements I was able to whittle the time it takes to run this code (on Chrome 18 on a series of ~600 items) down from over 3000 ms to 70ms.

The most drastic improvement came from using offsetWidth on the raw HTML elements instead of jQuery's outerWidth() statement. That alone shaved off over 50% of the time:

avatarsWidth = $listContents.find('div.avatars')[0].offsetWidth;

The second most drastic change came from reducing the number of DOM modifications I made. In the code above, I was looping through elements, calculating their widths, and then immediately applying these widths to the DOM. In my improved code, I still loop through to calculate the widths, however I then store those widths, detach the elements from the DOM, apply the stored widths, and reattach them. Thanks to @muffel for this idea. This shaved off over 30% of the total time:

$('#my_list div.text_field').each(function() {
  var $textField = $(this);
  // ...
  var newTextWidth = contentsWidth - textLeft - avatarsWidth - tagsWidth;
  $textField.attr('data-width', newTextWidth);
});

$('#my_list')
  .detach()
  .find('div.text_field')
    .each(function() {
      $(this).css({ width: $(this).attr('data-width') });
    })
    .end()
  .appendTo('#container');

The third biggest improvement came from reducing the number of times I traversed the DOM. Instead of selecting elements each time through the loop, I selected them all up front and then referenced the indices inside the loop. This made up the majority of the remaining improvement:

var $avatars = $('#my_list .avatars');
// ...
$('#my_list div.text_field').each(function(i) {
  // ...
  avatarsWidth = $avatars.eq(i).offsetWidth;
  // ...
});

Hope this helps someone!

Upvotes: 4

Ryan
Ryan

Reputation: 2815

for starters you shouldnt need to encase this with a $(this)... unless im missing something (which i might be) you should be able to just use "this" in each $textfield context... that will save you a function call and a var creation. Also move the var declarations out of the function... that will save more cycles too. Let me know if that speeds things up a bit.

Upvotes: 0

Related Questions