views:

167

answers:

5

The while statement in this function runs too slow (prevents page load for 4-5 seconds) in IE/firefox, but fast in safari...

It's measuring pixel width of text on a page and truncating until text reaches ideal width:

function constrain(text, ideal_width){

 $('.temp_item').html(text);
 var item_width = $('span.temp_item').width();
 var ideal = parseInt(ideal_width);
 var smaller_text = text;
 var original = text.length;

 while (item_width > ideal) {
  smaller_text = smaller_text.substr(0, (smaller_text.length-1));
  $('.temp_item').html(smaller_text);
  item_width = $('span.temp_item').width();
 }

 var final_length = smaller_text.length;
 if (final_length != original) {
  return (smaller_text + '…');
 } else {
    return text;
 }
}

Any way to improve performance? How would I convert this to a bubble-sort function?

Thanks!

+5  A: 

move the calls to $() outside of the loop, and store its result in a temporary variable. Running that function is going to be the slowest thing in your code, aside from the call to .html().

They work very very hard on making the selector engines in libraries fast, but it's still dog slow compared to normal javascript operations (like looking up a variable in the local scope) because it has to interact with the dom. Especially if you're using a class selector like that, jquery has to loop through basically every element in the document looking at each class attribute and running a regex on it. Every go round the loop! Get as much of that stuff out of your tight loops as you can. Webkit runs it fast because it has .getElementsByClassName while the other browsers don't. (yet).

Breton
This sure seems plausible, not having tried it myself. Wonder if the calls to html() to insert shorter strings in the DOM also are implicated (ie, do they trigger rendering)?
Jim Ferrans
Yes, that should work. It will make your code faster, but perhaps more importantly, easier to maintain, in keeping with the DRY principle.If, for example, you realize temp_item should actually be an id instead of a class, you would have to change the code in four places. Miss one and you may be pulling your hair out for a while. If you follow Breton's suggestion, you would only have to make one change.
Patrick McElhaney
+4  A: 

Instead of removing one character at time until you find the ideal width, you could use a binary search.

Patrick McElhaney
+2  A: 
Hugo Zapata
+2  A: 

Other than the suggestion by Breton, another possibility to speed up your algorithm would be to use a binary search on the text length. Currently you are decrementing the length by one character at a time - this is O(N) in the length of the string. Instead, use a search which will be O(log(N)).

Roughly speaking, something like this:

function constrain(text, ideal_width){

...

    var temp_item = $('.temp_item');
    var span_temp_item = $('span.temp_item');

    var text_len_lower = 0;
    var text_len_higher = smaller_text.length;

    while (true) {
          if (item_width > ideal)
          {
            // make smaller to the mean of "lower" and this
            text_len_higher = smaller_text.length;
            smaller_text = text.substr(0, 
                ((smaller_text.length + text_len_lower)/2));
          }
          else
          {
            if (smaller_text.length>=text_len_higher) break;

            // make larger to the mean of "higher" and this
            text_len_lower = smaller_text.length;
            smaller_text = text.substr(0, 
                ((smaller_text.length + text_len_higher)/2));
          }
          temp_item.html(smaller_text);
          item_width = span_temp_item.width();
    }

... }

1800 INFORMATION
Thanks, I'll try that!
novon
+1  A: 

One thing to note is that each time you add something to the DOM, or change the html in a node, the page has to redraw itself, which is an expensive operation. Moving any HTML updates outside of a loop might help speed things up quite a bit.

As other have mentioned, you could move the calls to $() to outside the loop. You can create a reference to the element, then just call the methods on it within the loop as 1800 INFORMATION mentioned.

If you use Firefox with the Firebug plugin, there's a great way of profiling the code to see what's taking the longest time. Just click profile under the first tab, do your action, then click profile again. It'll show a table with the time it took for each part of your code. Chances are you'll see a lot of things in the list that are in your js framework library; but you can isolate that as well with a little trial and error.

AdamFortuna