views:

74

answers:

4

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');
   ...
}
A: 

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.

MikeWyatt
i am making an individual call per element. The elements come from a search function that is somewhat complex for straight jQuery(criteria). I can send a whole array into highlighting, but like you saw the browser shows results after i'm done searching elemetns, adding highlight class to them. About 1-2 seconds too late.
Yev
You might still get a benefit from adding the elements to a jQuery array and calling addClass once at the end. But as I mentioned in my comment in your original question, I think your problem is in your search algorithm, not the loop that adds the class.
MikeWyatt
+1  A: 

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');
})();
Tomalak
jsFiddle is awesome!! Thanks for that unrelated tip.
Yev
+1  A: 
Bang Dao
A: 

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();
          });
      });
  });
edbond
I don't want to delay my other js functions purposely. The opposite, i want the browser to re-render the highlight as soon as I set it.
Yev
You can move this to html5 worker (http://blog.mozbox.org/post/2009/04/10/Web-Workers-in-action), but not all browsers supports them.Current javascript implementations single-threaded, they do one thing at a time. No concurrency allowed.
edbond