Elijah
Elijah

Reputation: 1322

jQuery.addClass() behaviour, all at once

I have this simple jQuery call to highlight a bunch of elements (couple thousand):

jQuery(elem).addClass('highlighted');

What the browser does is it quickly adds the class, but does not repaint the element. Is there a way to render each "highlighted" element as soon as i add the class. Right now the user sees a stutter until my loop of adding the class have finished.


EDIT:

More code:

var elem = findNext(); // walks the dom and finds next search match. ~10ms each call
while(elem){
   highlight(elem);
   elem = findNext(); 
}

function highlight(elem){   
   jQuery(elem).addClass('highlight');
   ...
}

Upvotes: 1

Views: 1992

Answers (4)

edbond
edbond

Reputation: 3951

Check out solution using only jQuery 1.4.2 (without setTimeout) It uses queue on $(document) to postpone function execution and execute them in order.

http://jsfiddle.net/xXGz3/

  $(function() {
      $('.highlight').each(function() {
          var e = $(this);
          $(document).delay(1000).queue(function(next) {
              e.addClass('a');
              next();
          });
      });
  });

Upvotes: 0

Tomalak
Tomalak

Reputation: 338316

This is because DOM changes are not rendered as long as a JavaScript function is running. User interface code is single-threaded and a browser locks while executing it. Normally this is not problem because JS is quite fast and functions do not run very long. But if they do, you see sluggish behavior as a result.

Your function needs to stop in the middle of its work to give the browser a chance to become responsive again. You can solve this by using setTimeout() and remembering where you left off.

This should give you an idea:

// prepares a closure function to handle affected elements in chunks of n
function updatePartial(elems, chunksize) {
  var current = 0;
  return function() {
    // changes n elements in a loop
    for (var i=0; i<chunksize; i++) {
      jQuery(elems[current+i]).addClass('highlighted');
    }
    current += chunksize;
    // calls itself again after a short break
    if (current < elems.length) setTimeout(arguments.callee, 10);
  }
}

// aquire and execute the closure function
updatePartial(jQuery("selector").get(), 100)();

(Tested at http://jsfiddle.net/fPdAg/)

Closures are an elegant way of avoiding global variables you would need in other implementations.


EDIT: A generalized version of the above would be this:

// prepares a closure function to handle affected elements in chunks of n
function updatePartial(elems, chunksize, payload) {
  var current = 0;
  return function() {
    // changes n elements in a loop
    for (var i=0; i<chunksize; i++) {
      // apply the payload function to current element
      payload.apply(elems[current+i]);
    }
    current += chunksize;
    // calls itself again after a short break
    if (current < elems.length) setTimeout(arguments.callee, 10);
  }
}

// aquire and execute the closure function, supply custom payload function
updatePartial(jQuery("selector").get(), 100, function() {
  jQuery(this).addClass('highlighted');
})();

Upvotes: 3

Bang Dao
Bang Dao

Reputation: 5102

I think the reason is because the browser behavior, not the JQuery problem. When call

jQuery(elem).addClass('highlighted');

While Javascript runnig, the browser stop working until JS finish its execution. So browser will not repain anything until JQuery addClass to every element.
Let try this trick

<script>
x = jQuery(elem).size();
y = number_item_each_time;
z = time_by_micro;
start = 0;
t = setInterval(function(){
      $(elem).slice(start, start + y).addClass('highlighted');
      start = start + y;
      if(start > x)
          clearInterval(t);
  }, z);
</script>

Upvotes: 1

MikeWyatt
MikeWyatt

Reputation: 7871

Are you sure the browser is quickly adding the class? It sounds like it takes a noticeable amount of time to complete the operation, which is what I'd expect.

You could try batching the addClass() calls into a series of setTimeout callbacks (e.g. 10 calls that each do 100 elements) to show the result more quickly.

Update

I just noticed that you said "until my loop of adding the class have finished". Are you making a single call to addClass, or calling it individually for each element inside a for loop?

You should definitely be making a single call a la jQuery('.all-your-elements').addClass('highlighted'). It will be more readable and much faster.

Upvotes: 0

Related Questions