Reputation: 9653
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
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:
$(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.maxHeight
/maxWidth
)? For reducing the reflow costs when changing the fontsize inside, an additional overflow:hidden
can help.Upvotes: 1
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):
Time: ~700
Current solution:
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