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.

8 Comments:

At 5:16 PM, Anonymous Anonymous 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 Anonymous 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 Anonymous 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: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

 

Post a Comment

<< Home