loading...

Line-Based Truncation Methods

carlymho profile image Carly Ho 🌈 ・5 min read

I frequently get the request to truncate content excerpts on websites I build to a certain number of lines. While it sounds like a straightforward task, it’s difficult to implement for two main reasons: firstly, since we build websites to scale responsively to a variety of device sizes, we can’t assume that a content box will be the same width at any given screen size, and secondly, unless we’re using a monospace font, the number of lines any text will take up is dependent on the content, since in most fonts characters have different widths.

Let’s take, for example, this snippet of code:

<article>
    <h3>This is a really long heading that we’re going to need to truncate for the sake of the layout since it goes on for several lines.</h3>
    <div class=“excerpt”>
        <p>Cras dolor orci, mollis eget mi vel, faucibus malesuada velit. Phasellus egestas nec nisi non bibendum. Vestibulum faucibus mauris ac ipsum viverra volutpat. Sed lobortis justo sapien, eget ornare orci convallis ut. Nullam pulvinar, urna at faucibus dignissim, orci elit varius quam, non sodales lacus elit a nunc. Morbi eleifend velit quis tellus tempus, sed vehicula neque vulputate. Vestibulum sit amet tempus nibh, sit amet semper mi. Integer sit amet quam eget ligula luctus pulvinar at non ligula. Suspendisse a fringilla lorem, cursus sodales odio. Aliquam ac odio eget nulla consectetur dictum eu quis massa. Sed volutpat ante non felis condimentum vestibulum. In tempor tristique varius. Nunc est nunc, tincidunt quis metus et, semper molestie eros. <a href=“#” class=“readmore”>Read More</a>
    </div>
</article>

What are some approaches we can take?

CSS-Based Clipping

A very simple solution would be to use CSS to set a maximum height for the container that the text is inside. If we know the line-height, we can multiply that by the number of lines we want to show to get the height the box should be to clip it properly.

h3 {
    line-height: 26px;
    height: 52px;
    overflow: hidden;
}

.content {
    line-height: 24px;
    height: 100%;
    overflow: hidden;
    max-height: 72px;
}

.content p {
    margin: 0;
}

This solution requires no javascript and therefore is very good for performance. You could also add a tiny amount of javascript to be able to reveal the hidden content by setting the max-height of the .content container to a height much longer than any content in it might be, such as 9999999px, which is also friendly to transition animations.

However, if you need to include a “More” or “Continue” link at the end, or want to add an ellipsis to indicate that the text has been truncated, you’ll need something a little more robust, since this solution will hide the end of a segment that happens to be over the specified number of lines long.

Pros: Minimum performance change, no modification of markup needed
Cons: Can’t use readmore links or ellipses at the end of the text, specific to certain CSS selectors

Javascript-Based Clipping

By using Javascript (and in this example, jQuery, although I’m sure you could write it without) to manipulate the HTML document, we can achieve more flexible results.

In this case, if we know, the line-height of the element and it remains constant, we can separate out any readmore links, split the text by spaces, and then iterate over each word until we find that the content is now taller than the box we want to fit it to. We should also store the original text in an attribute so that we can update the excerpt when the container size changes.

$(window).on(resize load, function() {
    $(.content p).each(function() {
        var lineHeight = 16; // content's line-height
        var lines = 3; // number of lines to show
        // only truncate if the text is longer than the desired size; if not, skip it
        if ($(this).height() > lineHeight * lines) {
            if ($(this).find('a.readmore').length > 0) {
                    $(this).attr('data-link', $(this).find('a.readmore')); // if there's a readmore link, separate it out and put it on a data attribute
                    $(this).find('a.readmore').detach(); // remove the link from the HTML
            }
            if ($(this).attr('title')) {
                    $(this).text($(this).attr('title'));
            }
            $(this).attr('title', $(this).text().trim()); // set the text as the title attribute to preserve it
            $(this).text(""); // empty the content
            var str = "";
            var prevstr = "";
            var words = text.split(" "); // split text into words
            var link = this.attr('data-link');
            for (var i = 0; i < words.length; i++) {
                if (this.height() > lines * lineHeight) {
                    // if we've spilled over onto the next line, roll it back a word and break the loop
                    this.html(prevstr.trim() + "&hellip; " + (typeof link !== 'undefined' ? ' ' + link : ''));
                    break;
                }
                prevstr = str;
                // update the working string with the next word
                str += words[i] + " ";
                // update the content in the document
                this.html(str.trim() + "&hellip;" + (typeof link !== 'undefined' ? ' ' + link : ''));
            }
            // if the last iteration caused us to spill over a line, roll back one word
            if (this.height() > lines * lineHeight) {
                this.html(prevstr.trim() + "&hellip; " + (typeof link !== 'undefined' ? ' ' + link : ''));
            }
        }
    });
});

Pros: Can maintain readmore links and ellipses
Cons: Specific to certain CSS selectors

Javascript Truncation on Arbitrary Items

The above solution is pretty flexible, but needs to specify the line-height. What if we want to apply the solution to an arbitrary element, or what if this particular element has a different line-height specified at some CSS breakpoints?

With many attributes one could just get the property, either from vanilla Javascript or by using the $(elt).css(“line-height”) method, but many browsers return the line-height value slightly differently, and, furthermore, we can’t guarantee what kind of units the line-height will be in.

I wish I had a really easy brilliant solution that anyone can DIY, but I got very tired and just downloaded the line-height module and included it ahead of my own scripting. (The line-height module was, unsurprisingly, also written to supplement a line-based truncation solution.)

Using that, we can replace the static number assigned to the lineHeight variable with window.lineHeight(this[0]);, which should return the line-height value in an easy-to-use way. At this point, this is pretty easy to turn into a custom function we can call with an element and a given number of lines, or even as a jQuery plugin we can use as a method on any element.

Pros: Maintains readmore links and ellipses, can be re-used easily
Cons: Uses an external library for line-height

Demo!

And here's the whole thing put together in a Codepen demo, with everything wrapped into a jQuery plugin:

Posted on by:

carlymho profile

Carly Ho 🌈

@carlymho

never met a part of the stack I didn't like. sr. engineer at clique studios in chicago, perpetual creative hobbyist, bird friend, local gay agenda promoter. she/her. tips: https://ko-fi.com/carlymho

Discussion

pic
Editor guide
 

Great post, I have a feeling future Google-searchers will stumble upon this a lot 😄

 

Jajaja I was one of them =) .... I was trying to implement that "read-more" behavior... Thanks for sharing this!

 

I am one google-searcher in 2019 lol

 

What about text-overflow: ellipsis ? Does that only work for single lines? And would it be better to use max-width: none instead of a huge number?

 

Yeah, text-overflow: ellipsis is a great solution if you only need one line, but unfortunately it doesn’t work for vertical overflow. (If they ever implement that, i’ll be delighted!)

The reason why I use a large max-height number here is that I often use the CSS solution in the case where there’s an “expand” button that the user can click to see the full content, and I’m usually asked to have it slide down with an animation. CSS transitions work on max-height and max-width if you’re transitioning between two numbers, but not from a number to the none value. If you’re not having to deal with animated transitions, though, max-height: none totally works!