Tom
Tom

Reputation: 9653

jQuery script efficiency

I use the below jQuery function named textfill on hundreds of divs. Basically it resizes the inner text to fit the enclosing div such that the font-size of the text is maximum, So longer texts are smaller than shorter ones but are at maximum font size that they can be without overflowing from the div.

; (function($) {
    /**
    * Resizes an inner element's font so that the inner element completely fills the outer element.
    * @version 0.1
    * @param {Object} Options which are maxFontPixels (default=40), innerTag (default='span')
    * @return All outer elements processed
    * @example <div class='mybigdiv filltext'><span>My Text To Resize</span></div>
    */
    $.fn.textfill = function(options) {
        var defaults = {
            maxFontPixels: 40,
            innerTag: 'span'
        };
        var Opts = jQuery.extend(defaults, options);
        return this.each(function() {
            var fontSize = Opts.maxFontPixels;
            var ourText = $(Opts.innerTag + ':visible:first', this);
            var maxHeight = $(this).height();
            var maxWidth = $(this).width();
            var textHeight;
            var textWidth;
            do {
                ourText.css('font-size', fontSize);

                textHeight = ourText.height();
                textWidth = ourText.width();
                fontSize = fontSize - 1;
            } while ((textHeight > maxHeight || textWidth > maxWidth) && fontSize > 3);
            var pos = (maxHeight-textHeight)/2;
            ourText.css('top', pos +'px');
        });
    };
})(jQuery);

Because I run this script on hundreds of divs that look like:

<div class="textDiv"><span>text appears here</span></div>

At the same time using:

$('.textDiv').each(function() { $(this).textfill({ maxFontPixels: 28 })});  

It takes 40 to 70 seconds depending on the amount of divs. I desperately need to tune the code so it will run faster. I've tried for the last two hours but can't seem to make it run faster. Can someone help?

EDIT:

Took some input from the comments and changed the code to:

var items = document.getElementsByClassName("textDiv");
for (var i = items.length; i--;) {
    $(items[i]).textfill({ maxFontPixels: 28 });
}

It seems to be a bit faster but still really slow.

Upvotes: 2

Views: 138

Answers (2)

Bergi
Bergi

Reputation: 665448

$('.textDiv').each(function() { $(this).textfill({ maxFontPixels: 28 })});

You're using the function wrong. Every (proper) plugin does already work on jQuery collections, and has the each built-in so that you do not need to put it around the invocation. Just do

$('.textDiv').textfill({ maxFontPixels: 28 });

Yet I think that is not your actual problem; loops are quite fast and even for hundred items it won't take seconds. The problem is

            ourText.css('font-size', fontSize);
            textHeight = ourText.height();
            textWidth = ourText.width();

inside a loop (actually in two nested loops), as it requires a complete reflow by the browser. You will need to minimize the calls to this part, for example by using some kind of binary search (bisection) and/or by applying a interpolation metric that approximates the font size (number of characters divided by area for example?) to get a good starting value.

Beside that, there might be other minor optimisations:

  • cache $(this)
  • $(Opts.innerTag + ':visible:first', this); looks like a quite complex selector. Is that really necessary, do you expect hidden elements? Move such extras to the options, and go with $(this).children().first() by default.
  • I'm not sure about your CSS, but how do you set the dimensions of your divs (which you retrieve as maxHeight/maxWidth)? For reducing the reflow costs when changing the fontsize inside, an additional overflow:hidden can help.

Upvotes: 1

Xeon
Xeon

Reputation: 5989

Obviously the bottle neck is the inner-most looping (the next is parent of inner-most and so on).

Why don't use "bisection" for finding out the font size?:

For 200 divs:


Bisect solution (needs some refactoring):

http://jsfiddle.net/nx2n2/8/

Time: ~700

Current solution:

http://jsfiddle.net/pXL5z/3/

Time: ~1400

The most important code:

        var change = Math.ceil(fontSize / 2);
        while(true) {
            change = Math.ceil(change / 2);

            var prev = fontSize;
            do {
                fontSize = fontSize - change;
                ourText.css('font-size', fontSize);

                textHeight = ourText.height();
                textWidth = ourText.width();
            } while ((textHeight > maxHeight || textWidth > maxWidth) && fontSize > 3);

            change = Math.ceil(change / 2);            
            while (textHeight < maxHeight && textWidth < maxWidth) {
                fontSize = fontSize + change;
                ourText.css('font-size', fontSize);

                textHeight = ourText.height();
                textWidth = ourText.width();
            }

            var current = fontSize;
            if(prev == current) {
                break;
            }
        }

        // this is because you subtract after change in your original solution
        // only for 'compatibility' with original solution
        fontSize = fontSize - 1;

Upvotes: 0

Related Questions