Friday, April 22, 2005

Ugliest line trunc hack EVER

Warning: this hack is dumb and should not be used by anyone.

The challenge: given some text of abitrary length, figure out where to best truncate the text so that only the first two lines are displayed, regardless of the width of the containing element.

By the way, the element is inline, you have no control over its dimensions, and the spot where you truncate the text must end in an ellipsis (…) Don't ask. Q: can't we just cut it at 20 characters and call it a day? A: no, it must fully fill the two lines, do as we say or we will be cranky.

Given that the fonts used were not fixed-width, I threw out the idea of counting character widths. However, I knew that the line-height CSS rule for this element was 14 pixels, and using offsetHeight I could determine if the height of the containing element, in this case a SPAN. (Word to the wise: offsetHeight is what you want. In the absence of a specific rule, the computed CSS height property for an element seems to be either blank, undefined or "auto", none of which are particularly helpful.)

Armed with this knowledge, I set out to write the ugliest hack I could manage:

var elm = document.getElementById("DivOne");
while (parseInt(elm.offsetHeight) > 28){
    var txt = elm.innerHTML;
    txt = txt.substring(0, txt.length-2) + "…";
    elm.innerHTML = txt;
}

In a nutshell: take the text, shave off the last character, append an ellipsis HTML entity, then shove the text back into the element and measure the height. Repeat until the SPAN is finally 28px high (twice the line height == two visible lines).

Ugh. I'm lucky that the strings I was working with were no more than 30 characters long, and only 3-5 strings had to be dealt with in any given page. Excuse me while I go take a shower.

14 Comments:

At 5:16 PM, Anonymous Rory Parle said...

Couldn't you get a better first estimate by dividing the string's length by the ratio of the current height to the required height? Then fiddle around with what's left.

 
At 8:25 PM, Blogger scottandrew said...

Hm, I don't follow. Sample code?

 
At 10:20 PM, Blogger Jonathan Snook said...

I think he was suggesting to do something like:

var elm = document.getElementById("DivOne");
var txt = elm.innerHTML;
txt = txt.substring(0, txt.length - txt.length / (parseInt(elm.offsetHeight)/28)) + "…";
elm.innerHTML = txt;
}

Likely my math is off but hopefully you get the idea. Basically, you take the existing height of the box and divide it by the height that you want the box to be. That's your ratio. Then take the actual line text length and minus the same ratio amount.

The problem with this solution is if the last night line of text (likely) doesn't fill the entire line. In which case, you're text is unlikely to completely fill 2 lines. Although, a quick estimate on average character width and text length could also be factored in as some sort of weighted system.

 
At 11:17 PM, Anonymous Anonymous said...

Bleah!
Aren't real world requirements horrible? :)

sil

 
At 9:16 AM, Anonymous Rory Parle said...

Jonathan seems to have understood me. I did say you'd have to fiddle around with what's left, ie. put back in some characters one at a time until it got too long, then drop the last one again.

 
At 11:20 AM, Blogger scottandrew said...

Rory: not a bad solution at all. I'll give it a try and see how close it fits the requirement. It's certainly a cleaner approach than what I came up with. :)

 
At 10:52 AM, Anonymous Ed said...

Hmm. That is ugly. I hadn't ever really thought about doing something like that- I usually just avoid the issue. I've had clients ask for something like this in the past and my solution was much more elegant; I just tell them "that cannot be done". :)

 
At 6:14 PM, Blogger Erik Arvidsson said...

I remember doing the same thing before MS introduced this as a feature in IE6. I used a divide and conquer algorithm to find the needed string but I think the solution with the ratio might actually be fast enough.

Here is an alternative that should work pretty well with solid background color. Set height and set overflow to hidden. In this way you can control that only 2 lines are shown. Then you can add the '...' as relative positioned sub element..

 
At 6:57 PM, Blogger Nick said...

Here is an alternative that should work pretty well with solid background color. Set height and set overflow to hidden. In this way you can control that only 2 lines are shown. Then you can add the '...' as relative positioned sub element..

however that requires the initial string is too long, and kinda would look weird with your ellipisies directly on top of the end text

 
At 8:51 PM, Blogger Ricky said...

I have taken all of your ideas and came out with something like this

function Method1(numLine) {
var cnt = 0;
var height = numLine*charHeight;

if((parseInt(elm.offsetHeight)/charHeight)>numLine+1) { //use ratio on first estimate
var txt = elm.innerHTML;
txt = txt.substring(0, txt.length / (parseInt(elm.offsetHeight)/height-1)) + "…";
elm.innerHTML = txt;
++cnt;
}
while (parseInt(elm.offsetHeight) > height){
var txt = elm.innerHTML;
txt = txt.substring(0, txt.length-2) + "…";
elm.innerHTML = txt;
++cnt;
}
res.innerHTML = cnt.toString() + " tries"
}

Ricky Supit

 
At 7:10 AM, Blogger HiiFii Webservices said...

I wanted to show you some superb resourses on the net.
Learn to earn 90000$/Month
For which you may also see my Personal Website
Here.
and for a Personal Education Career Tools
free Study Database.
This site is for seeing the
Hifi Electronics.
And this is for
World Class Gadgets

 
At 11:06 AM, Blogger HiiFii Webservices said...

Please visit Ayesha Takia's Fan Site and tell us how it is - Ayesha World

 
At 6:52 PM, Blogger George Charlson said...

Info on picture of zoroastrianism

 
At 6:01 PM, Blogger satxxkenn said...

Cool blog - Check out my site if you need help finding a good low interest credit card. Credit Cards

 

Post a Comment

<< Home