I'm trying to use jquery to write a fast function that calculates the pixel width of a string on a html page, then truncates the string until it reaches an ideal pixel width...
However it's not working (the text doesn't truncate)...
Here is the code I have:
function constrain(text, original, ideal_width){
var temp_item = ('<span class="temp_item" style="display:none;">'+ text +'</span>');
$(temp_item).appendTo('body');
var item_width = $('span.temp_item').width();
var ideal = parseInt(ideal_width);
var smaller_text = text;
while (item_width > ideal) {
smaller_text = smaller_text.substr(0, (smaller_text-1));
$('.temp_item').html(text);
item_width = $('span.temp_item').width();
}
var final_length = smaller_text.length;
if (final_length != original) {
return (smaller_text + '…');
} else {
return text;
}
}
Here's how I'm calling it from the page:
$('.service_link span:odd').each(function(){
var item_text = $(this).text();
var original_length = item_text.length;
var constrained = constrain(item_text, original_length,175);
$(this).html(constrained);
});
Any ideas on what I'm doing wrong? If there is a way to do it faster (ie bubble sort), that would be great too.
Thanks!
Rather than hacking this together with script, why not just use CSS to specify the width of the element you're putting this string into? You can use text-overflow to tell the browser it should truncate with an ellipsis.
.truncated { display:inline-block; max-width:100px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
text-overflow is a CSS3 declaration, but is supported in IE 6+, WebKit 312.3+ (Safari 1.3+/Chrome), and Opera 9+ (versions < 10.7 need the -o-text-overflow declaration).
Note that this was unimplemented in Firefox until 7.0, and under 4% of Firefox users are still using old versions (mostly 3.6). Your text will still be truncated in older versions, there just won't be an ellipsis rendered. If you're concerned about this small group of users, you can use this jQuery plugin, which does nothing in IE/WebKit/Opera/Firefox ≥ 7, but will emulate text-overflow in Firefox ≤ 6.
On this line:
smaller_text = smaller_text.substr(0, (smaller_text-1));
you may have intended
smaller_text = smaller_text.substr(0, (smaller_text.length-1));
Does that solve the problem?
Thanks, I got it working - but it only works on the first item and stops, does this have to do with the way I'm declaring variables?
here's the current code:
function constrain(text, original, ideal_width){
var temp_item = ('<span class="temp_item" style="display:none;">'+ text +'</span>');
$(temp_item).appendTo('body');
var item_width = $('span.temp_item').width();
var ideal = parseInt(ideal_width);
var smaller_text = text;
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;
}
}
The function takes the text to check, the ideal_width in pixel and the class name for the css. If the ideal_width is less than the text width it truncs and adds the hellip otherwise it returns the text unmodified. Simple and works! :-)
function constrain(text, ideal_width, className){
var temp_item = ('<span class="'+className+'_hide" style="display:none;">'+ text +'</span>');
$(temp_item).appendTo('body');
var item_width = $('span.'+className+'_hide').width();
var ideal = parseInt(ideal_width);
var smaller_text = text;
if (item_width>ideal_width){
while (item_width > ideal) {
smaller_text = smaller_text.substr(0, (smaller_text.length-1));
$('.'+className+'_hide').html(smaller_text);
item_width = $('span.'+className+'_hide').width();
}
smaller_text = smaller_text + '…'
}
$('span.'+className+'_hide').remove();
return smaller_text;
}
If you have the text, and know the size you want, why not just use substr?
I did something similar with
$('#history-1').text( $('#history-1').text().trim().substr(0,32) + "..." )
Related
I want to paginate a text in some div so it will fit the allowed area
Logic is pretty simple:
1. split text into words
2. add word by word into and calculate element height
3. if we exceed the height - create next page
It works quite good
here is JS function i've used:
function paginate() {
var newPage = $('<pre class="text-page" />');
contentBox.empty().append(newPage);
var betterPageText='';
var pageNum = 0;
var isNewPage = false;
var lineHeight = parseInt(contentBox.css('line-height'), 10);
var wantedHeight = contentBox.height() - lineHeight;
for (var i = 0; i < words.length; i++) {
if (isNewPage) {
isNewPage = false;
} else {
betterPageText = betterPageText + ' ' + words[i];
}
newPage.text(betterPageText + ' ...');
if (newPage.height() >= wantedHeight) {
pageNum++;
if (pageNum > 0) {
betterPageText = betterPageText + ' ...';
}
newPage.text(betterPageText);
newPage.clone().insertBefore(newPage)
betterPageText = '...';
isNewPage = true;
} else {
newPage.text(betterPageText);
}
}
contentBox.craftyslide({ height: wantedHeight });
}
But when i add an image it break everything. In this case text overflows 'green' area.
Working fiddle: http://jsfiddle.net/74W4N/7/
Is there a better way to paginate the text and calculate element height?
Except the fact that there are many more variables to calculate,not just only the word width & height, but also new lines,margins paddings and how each browser outputs everything.
Then by adding an image (almost impossible if the image is higher or larger as the max width or height) if it's smaller it also has margins/paddings. and it could start at the end of a line and so break up everything again.basically only on the first page you could add an image simply by calculating it's width+margin and height+margin/lineheight. but that needs alot math to get the wanted result.
Said that i tried some time ago to write a similar script but stopped cause of to many problems and different browser results.
Now reading your question i came across something that i read some time ago:
-webkit-column-count
so i made a different approach of your function that leaves out all this calculations.
don't judge the code as i wrote it just now.(i tested on chrome, other browsers need different prefixes.)
var div=document.getElementsByTagName('div')[0].firstChild,
maxWidth=300,
maxHeigth=200,
div.style.width=maxWidth+'px';
currentHeight=div.offsetHeight;
columns=Math.ceil(currentHeight/maxHeigth);
div.style['-webkit-column-count']=columns;
div.style.width=(maxWidth*columns)+'px';
div.style['-webkit-transition']='all 700ms ease';
div.style['-webkit-column-gap']='0px';
//if you change the column-gap you need to
//add padding before calculating the normal div.
//also the line height should be an integer that
// is divisible of the max height
here is an Example
http://jsfiddle.net/HNF3d/10/
adding an image smaller than the max height & width in the first page would not mess up everything.
and it looks like it's supported by all modern browsers now.(with the correct prefixes)
In my experience, trying to calculate and reposition text in HTML is almost an exercise in futility. There are too many variations among browsers, operating systems, and font issues.
My suggestion would be to take advantage of the overflow CSS property. This, combined with using em sizing for heights, should allow you to define a div block that only shows a defined number of lines (regardless of the size and type of the font). Combine this with a bit of javascript to scroll the containing div element, and you have pagination.
I've hacked together a quick proof of concept in JSFiddle, which you can see here: http://jsfiddle.net/8CMzY/1/
It's missing a previous button and a way of showing the number of pages, but these should be very simple additions.
EDIT: I originally linked to the wrong version for the JSFiddle concept
Solved by using jQuery.clone() method and performing all calculations on hidden copy of original HTML element
function paginate() {
var section = $('.section');
var cloneSection = section.clone().insertAfter(section).css({ position: 'absolute', left: -9999, width: section.width(), zIndex: -999 });
cloneSection.css({ width: section.width() });
var descBox = cloneSection.find('.holder-description').css({ height: 'auto' });
var newPage = $('<pre class="text-page" />');
contentBox.empty();
descBox.empty();
var betterPageText = '';
var pageNum = 0;
var isNewPage = false;
var lineHeight = parseInt(contentBox.css('line-height'), 10);
var wantedHeight = contentBox.height() - lineHeight;
var oldText = '';
for (var i = 0; i < words.length; i++) {
if (isNewPage) {
isNewPage = false;
descBox.empty();
}
betterPageText = betterPageText + ' ' + words[i];
oldText = betterPageText;
descBox.text(betterPageText + ' ...');
if (descBox.height() >= wantedHeight) {
if (i != words.length - 1) {
pageNum++;
if (pageNum > 0) {
betterPageText = betterPageText + ' ...';
}
oldText += ' ... ';
}
newPage.text(oldText);
newPage.clone().appendTo(contentBox);
betterPageText = '... ';
isNewPage = true;
} else {
descBox.text(betterPageText);
if (i == words.length - 1) {
newPage.text(betterPageText).appendTo(contentBox);
}
}
}
if (pageNum > 0) {
contentBox.craftyslide({ height: wantedHeight });
}
cloneSection.remove();
}
live demo: http://jsfiddle.net/74W4N/19/
I actually came to an easier solution based on what #cocco has done, which also works in IE9.
For me it was important to keep the backward compatibility and the animation and so on was irrelevant so I stripped them down. You can see it here: http://jsfiddle.net/HNF3d/63/
heart of it is the fact that I dont limit height and present horizontal pagination as vertical.
var parentDiv = div = document.getElementsByTagName('div')[0];
var div = parentDiv.firstChild,
maxWidth = 300,
maxHeigth = 200,
t = function (e) {
div.style.webkitTransform = 'translate(0,-' + ((e.target.textContent * 1 - 1) * maxHeigth) + 'px)';
div.style["-ms-transform"] = 'translate(0,-' + ((e.target.textContent * 1 - 1) * maxHeigth) + 'px)';
};
div.style.width = maxWidth + 'px';
currentHeight = div.offsetHeight;
columns = Math.ceil(currentHeight / maxHeigth);
links = [];
while (columns--) {
links[columns] = '<span>' + (columns + 1) + '</span>';
}
var l = document.createElement('div');
l.innerHTML = links.join('');
l.onclick = t;
document.body.appendChild(l)
I'm developing an app for a TV with an old Gecko engine (1.9 I think). I have a container 250x40 and I'd like to fit 2 lines of text in it, and if it's too long then an ellipsis should be shown (just like in the CSS3 property text-overflow: ellipsis).
However:
- I cannot use CSS3,
- I tried using some jQuery plugins, but they just work too slow - you can accually see the text being shrunk down until it fits in the container.
I tried counting letters, but it doesn't work, because they are all different widths.
I tried mapping each letter to its width, and counting the widht of the whole text, but the fact that it's 2 lines screws it all up - I don't know at which point the text will go to the next line.
Any help appreciated.
Slightly based on #Emissary's answer, here's a reasonably performing pair of jQuery plugins that'll handle adding ellipsis on elements that might hold more than one row of text:
(function($) {
$.fn.reflow = function() {
return this.each(function() {
var $this = $(this);
var $parent = $this.parent();
var text = $this.data('reflow');
$this.text(text); // try full text again
var words = text.split(' ');
while ($this.height() > $parent.height() ||
$this.width() > $parent.width()) {
words.pop();
$this.html(words.join(' ') + '…');
}
});
}
$.fn.overflow = function() {
return this.each(function() {
var $this = $(this);
var text = $this.text();
$this.data('reflow', text);
}).reflow();
}
})(jQuery);
The latter one registers elements for reflowing, and the first one actually does the work. The split is there to allow window resizing to automatically reformat the (original) contents of the element, e.g.:
$('.overflow').overflow();
$(window).on('resize', function() {
$('.overflow').reflow();
});
For higher performance, if it matters, you might consider replacing the .pop sequence with something that uses a O(log n) binary partitioning algorithm to find the optimal cut point instead of just removing one word at a time.
See http://jsfiddle.net/alnitak/vyth5/
It's been a while since I bothered with supporting older browsers but this is how I always did it. Should point out that it trims words rather than characters, I always thought half a word looked daft - if you care about typography...
html:
<div id="el">
<span class="overflow">Lorem ipsum dolor sit amet</span>
</div>
css:
#el {
width: 250px;
height: 40px;
overflow: hidden;
}
.overflow {
white-space: nowrap;
}
js / jQuery:
var el = $('#el'),
ov = $('#el .overflow'),
w = el.text().split(' ');
while(ov.width() > el.width()){
w.pop();
ov.html( w.join(' ') + '…' );
}
jsFiddle
This chap here has a solution that uses javascript and no JQuery: http://blog.mastykarz.nl/measuring-the-length-of-a-string-in-pixels-using-javascript/
Done in jsfiddle: http://jsfiddle.net/ZfDYG/
Edit - just read the bit about 2 lines of text: http://jsfiddle.net/ZfDYG/8/
code (for completeness):
String.prototype.visualLength = function() {
var ruler = $("ruler");
ruler.innerHTML = this;
return ruler.offsetWidth;
}
function $(id) {
return document.getElementById(id);
}
var s = "Some text that is quite long and probably too long to fit in the box";
var len = s.visualLength();
String.prototype.trimToPx = function(length,postfix) {
postfix = postfix || '';
var tmp = this;
var trimmed = this;
if (tmp.visualLength() > length) {
trimmed += postfix;
while (trimmed.visualLength() > length) {
tmp = tmp.substring(0, tmp.length-1);
trimmed = tmp + postfix;
}
}
return trimmed;
}
String.prototype.wrapToLength = function(complete) {
if(complete[this.length] == ' ' || complete[this.length - 1] == ' ') return;
var wrapped = this;
var found = false;
for(var i = this.length-1; i > 0 && !found; i--) {
if(this[i] == ' ') {
wrapped = this.substring(0, i);
found = true;
}
}
return wrapped;
}
var firstLine = s.trimToPx(240).wrapToLength(s);
var secondLine = s.substring(firstLine.length, s.length);
$('output').innerHTML= firstLine+' '+secondLine.trimToPx(240,'...');
Html:
<span id="ruler"></span>
<div id="output"></div>
css:
#ruler { visibility: hidden; white-space: nowrap; }
#output {
width: 240px;
height: 50px;
border: 1px solid red;
}
If that is still too slow on your box, I guess it should be possible to speed up the while loop by starting with bigger additions to oscillate towards the final length (kinda like a spring) rather than increment slowly up to it.
In centered h1 elements, if the text falls on multiple lines, line breaks make the text look like this:
This is a header that takes up two
lines
This is a header that takes up three
lines because it is really, really
long
Is there a way to manipulate these elements so that the length of the lines of text is roughly equal? Like this:
This is a header that
takes up two lines
This is a header that takes
up three lines because it
is really, really long
The jQuery plugin Widow Fix prevents single-word lines, but I'm looking for something that evens out all the lines in a multi-line element. Are there any jQuery plugins for this, or can you recommend a strategy?
I would solve it using only strict JavaScript, going this way:
1. put a class named 'truncate' to h1 tags you want to break
2. configure the javascript code on your needs knowing that
MAXCOUNT: (integer) max chars counted per line
COUNT_SPACES: (boolean) white spaces must be counted?
COUNT_PUNCTUATION: (boolean) punctuation must be counted?
EXACT: (boolean) the last word must be cut?
BLOCKS_CLASS: (string) the className of the h1 to consider
I wrote the code very quickly so it must be better tested for bugs,
but it can be a starting point I think.
I'm not using jQuery in this code to keep the code light and to avoid dependencies.
I think I'm using all cross-browser commands (cannot test it I've got only linux now). However any correction for cross-browser compatibility task (included the use of jQUery if requested) might be easy.
Here is the code:
<html>
<head>
<style>
h1 {background-color: yellow;}
#hiddenDiv {background-color: yellow; display: table-cell; visibility:hidden;}
</style>
<script>
var MAXCOUNT = 20;
var COUNT_SPACES = false;
var EXACT = false;
var COUNT_PUNCTUATION = true;
var BLOCKS_CLASS = 'truncate';
window.onload = function ()
{
var hidden = document.getElementById('hiddenDiv');
if (hidden == null)
{
hidden = document.createElement('div');
hidden.id = 'hiddenDiv';
document.body.appendChild(hidden);
}
var blocks = document.getElementsByClassName(BLOCKS_CLASS);
for (var i=0; i<blocks.length; i++)
{
var block = blocks[i];
var text = block.innerHTML;
var truncate = '';
var html_tag = false;
var special_char = false;
maxcount = MAXCOUNT;
for (var x=0; x<maxcount; x++)
{
var previous_char = (x>0) ? text.charAt(x-1) : '';
var current_char = text.charAt(x);
// Closing HTML tags
if (current_char == '>' && html_tag)
{
html_tag = false;
maxcount++;
continue;
}
// Closing special chars
if (current_char == ';' && special_char)
{
special_char = false;
maxcount++;
continue;
}
// Jumping HTML tags
if (html_tag)
{
maxcount++;
continue;
}
// Jumping special chars
if (special_char)
{
maxcount++;
continue;
}
// Checking for HTML tags
if (current_char == '<')
{
var next = text.substring(x,text.indexOf('>')+1);
var regex = /(^<\w+[^>]*>$)/gi;
var matches = regex.exec(next);
if (matches[0])
{
html_tag = true;
maxcount++;
continue;
}
}
// Checking for special chars
if (current_char == '&')
{
var next = text.substring(x,text.indexOf(';')+1);
var regex = /(^&#{0,1}[0-9a-z]+;$)/gi;
var matches = regex.exec(next);
if (matches[0])
{
special_char = true;
maxcount++;
continue;
}
}
// Shrink multiple white spaces into a single white space
if (current_char == ' ' && previous_char == ' ')
{
maxcount++;
continue;
}
// Jump new lines
if (current_char.match(/\n/))
{
maxcount++;
continue;
}
if (current_char == ' ')
{
// End of the last word
if (x == maxcount-1 && !EXACT) { break; }
// Must I count white spaces?
if ( !COUNT_SPACES ) { maxcount++; }
}
// Must I count punctuation?
if (current_char.match(/\W/) && current_char != ' ' && !COUNT_PUNCTUATION)
{
maxcount++;
}
// Adding this char
truncate += current_char;
// Must I cut exactly?
if (!EXACT && x == maxcount-1) { maxcount++; }
}
hidden.innerHTML = '<h1><nobr>'+truncate+'</nobr></h1>';
block.style.width = hidden.offsetWidth+"px";
}
}
</script>
</head>
<body>
<center>
<h1 class="truncate">
This is a header that
takes up two lines
</h1>
<br>
<h1 class="truncate">
This is a header that takes
up three lines because it
is really, really long
</h1>
<br>
<h1>
This is a header pretty short
or pretty long ... still undecided
which in any case is not truncated!
</h1>
</center>
</body>
</html>
And here is a demo of that: http://jsfiddle.net/6rtdF/
Late to this party, but here's my approach. I get the initial element height (any elements with the class balance_lines, in the code below), then incrementally shrink the width of the element. Once the height of the element changes, I've gone too far. The step before that should have lovely roughly-equal line lengths.
$('.balance_lines').each(function(){
var currentHeight = $(this).height();
var thisHeight = currentHeight;
var currentWidth = $(this).width();
var newWidth = currentWidth;
// Try shrinking width until height changes
while (thisHeight == currentHeight) {
var testWidth = newWidth - 10;
$(this).width(testWidth);
thisHeight = $(this).height();
if (thisHeight == currentHeight) {
newWidth = testWidth;
} else {
break;
}
}
$(this).width(newWidth);
});
You can see this code in action on the homepage at apollopad.com.
The CSS Text 4 draft proposes text-wrap: balance, but I don't think any browser implements it yet.
In the meantime, you can use Adobe's jQuery plugin (demo): https://github.com/adobe-webplatform/balance-text
I'm implementing an auto textarea resizer that auto-expands a textarea when user presses ENTER and after a minimum scrollHeight size.
It works well in IE, Firefox and Opera, but not in Chrome.
In Chrome the textarea is resized at any keypress (not only ENTER) because when settings e.style.height it also changes scrollHeight (this doesn't happen to occur on other browsers.
I marked the problematic line with a comment:
function resize(e)
{
e = e.target || e;
var $e = $(e);
console.log( 'scrollHeight:'+e.scrollHeight );
console.log( 'style.height:'+e.style.height );
var h = Math.min( e.scrollHeight, $e.data('textexp-max') );
h = Math.max( h, $e.data('textexp-min') );
if( $e.data('h')!=h )
{
e.style.height = "";
e.style.height = h + "px"; //this changes the scrollHeight even if style.height is smaller thatn scrollHeight
//e.style.overflow = (e.scrollHeight > h ? "auto" : "hidden");
$e.data('h',h);
}
return true;
}
Here you'll find full code: http://jsfiddle.net/NzTYd/9/
UPDATE
I've found that when I update the e.style.height in Firefox/IE/Opera, e.scrollHeight is updated to the exact same value:
Example:
Setting 80px to e.style.height, sets to 80px e.scrollHeight
However, Chrome updates e.scrollHeight with a higher value (4px more).
Example::
Settings 80px to e.style.height, sets to 84px e.scrollHeight
This causes a textarea that is growing 4px for EVERY keypress it receives!
You can get the answer to this here.
Textarea to resize based on content length
Ok, I've found there is a bug with webkit: http://code.google.com/p/chromium/issues/detail?id=34224
Finally I solve it detecting if e.scrollHeight changed an amount of only 4 px, which is the difference chrome is adding.
If that condition is arised, I substract 4px from e.style.height to fix it.
I built an auto-expanding textarea in jQuery a few months back and haved used it with great success on many sites. This doesn't address your issue directly, but you might find it helpful.
If the user provides a ## a paragraph is inserted and extra line breaks are removed.
I have my textarea set to fit 40 characters per line. The line increments 1px at a time to accommodate the new characters.
You can see a working example on my site at evikjames.com > jQuery examples > Expandable Textarea.
$(".Expandable").live("keyup", function() {
// ADJUST ROWS ROWS
var ThisText = $(this).val();
var Rows = calculateRows(ThisText);
$(this).attr("rows", Rows);
// CALCULTE COUNTER
var ThisTextMax = $(this).attr("max");
if (ThisText.length > ThisTextMax) {
ThisText = ThisText.substring(ThisText, ThisTextMax);
$(this).val(ThisText);
alert("You have exceeded the maximum allowable text. Revise!");
}
$(this).next(".Count").html(ThisText.length + "/" + ThisTextMax + " characters.");
});
var calculateRows = function calculateRows(String) {
var NumCharacters = String.length;
var NumRows = Math.ceil(NumCharacters/40);
var Match = String.match(/##/g);
var NumParagraphs = 0;
if (Match != null) {
NumParagraphs = String.match(/##/g).length;
NumRows = NumRows + NumParagraphs;
}
return NumRows;
}
I'm trying to create a universal function that I can call from multiple places to truncate long text recursively to fit a predefined pixel width - using jquery.
Here is the code...
function constrain(text, original, ideal_width){
var ideal = parseInt(ideal_width);
$('span.temp_item').remove();
var temp_item = ('<span class="temp_item" style="display:none">'+ text +'</span>');
var item_length = text.length;
$(temp_item).appendTo('body');
var item_width = $('span.temp_item').width();
if (item_width > ideal) {
var smaller_text = text.substr(0, (item_length-1));
return constrain(smaller_text, original);
} else if (item_length != original) {
return (text + '…');
} else if (item_length == original) {
return text;
}
}
If I run the function like this:
$('.service_link span:odd').each(function(){
var item_text = $(this).text();
var original_length = item_text.length;
var constrained = constrain(item_text, original_length,'175');
$(this).html(constrained);
});
The text doesn't truncate. I also tried the 175 without the quotes.
If I define var ideal = 175; inside the function, then it works. Why is passing 175 to the function not working? I did a parseInt on it in case it was a string.
Also - this truncate code run a bit slow on older machines - any tips for speeding it up?
Thanks!
Great stuff here. I used the function by Phil Carter. I just wanted the new string with the &hellip to be truncated at the same width as the rest.
I just quickly added another temp-width lookup and recursive call. Could use some cleanup but it works.
here's the new while:
while(item_width > ideal) {
var smaller_text = text.substr(0, (item_length-1));
return constrain(smaller_text, original, ideal_width, counter);
}
if (item_length != original) {
new_text=text+'…';
$('span.temp_item').remove();
var temp_item = ('<span class="temp_item" style="display:none">'+ new_text +'</span>');
$(temp_item).appendTo('body');
var item_width_new = $('span.temp_item').width();
if(item_width_new>ideal){
var smaller_text = text.substr(0, (item_length-1));
return constrain(smaller_text, original, ideal_width, counter);
}
else {
return new_text;
}
} else if (item_length == original) {
return text;
}
}
What happens when the visitor to your site presses "ctl +" ? It's my (probably out of date) belief that you're supposed to use "em" sizes for font containers, so they scale.
Ah... found the bug - forgot to pass the recursive part the ideal width:
return constrain(smaller_text, original, ideal);
TOTAL WE WRITE
So I decided that your iteration over the lorum ipsum text in 5 spans, taking 16 secs was far too long, so thought how to speed this up. and I have it down to 0.4 seconds.
function constrain(text, original, ideal_width, counter){
var ideal = parseInt(ideal_width);
$('span.temp_item').remove();
var temp_item = ('<span class="temp_item" style="display:none">'+ text +'</span>');
var item_length = text.length;
$(temp_item).appendTo('body');
var item_width = $('span.temp_item').width();
if(counter == 0) {
//work out some ranges
var temp_item = ('<span class="temp_item_i" style="display:none">i</span>');
$(temp_item).appendTo('body');
var i_width = $('span.temp_item_i').width();
var max_i = Math.round((ideal_width / i_width) + 1);
var temp_item = ('<span class="temp_item_m" style="display:none">M</span>');
$(temp_item).appendTo('body');
var m_width = $('span.temp_item_m').width();
var max_m = Math.round((ideal_width / m_width) + 1);
text = text.substr(0, (max_i - max_m));
var item_length = text.length;
}
counter++;
while(item_width > ideal) {
var smaller_text = text.substr(0, (item_length-1));
return constrain(smaller_text, original, ideal_width, counter);
}
if (item_length != original) {
return (text + '…');
} else if (item_length == original) {
return text;
}
}
$(document).ready(function() {
var d = new Date();
var s = d.getTime();
$('.service_link').each(function(){
var item_text = $(this).text();
var original_length = item_text.length;
var constrained = constrain(item_text, original_length, 175, 0);
$(this).html(constrained);
});
var e = d.getTime()
alert('Time Taken: ' + ((e - s)/1000));
});
Basically on the first run, it works out how many lowercase i's and how many uppercase Ms fit in the space, and then restricts the text length to that, this reduces the number of iterations massively.
Hope this helps.