DEV Community

Cover image for Line Clampin'(Truncating Multiline Text) - the smarter way
Evgenia Milcheva
Evgenia Milcheva

Posted on

Line Clampin'(Truncating Multiline Text) - the smarter way

Line Clampin' is especially useful for cards (news, blogs and etc.) info text where you'd like to keep the cards equal height and thus all text that is bigger than your strict max number of lines to be cut off.

Please mind that I came across so many JS libraries that tend to do this technique and I'm really not a fan of bloating my projects with unneeded/overcomplicated solitions, not to mention the possible negative effect on load times and rendering speed 🐌.

I'm also one of those front-ends that still have to support legacy browsers like IE11 (poor me 😀) and so let me present you my approach that is lightweight, effective and modern + legacy browsers compatible.

TL;DR -- check my codepen demonstrating the approach in action https://codepen.io/porg/pen/VwLmOpV

Let's say we want to cut off the text on the 3-rd line (anything bigger is cut off and ellipsized). How big is a line though? Well it depends on the font-size of the text and it's line-height. We got everything we need to do a mixin that does our job:

@mixin limitTextToLineNumbersMixin( $font-size: $font-size-base, $line-height: 1.2, $lines-to-show: 3 ) {
  max-width: 100%;
  height: calc(#{$font-size} * #{$line-height} * #{$lines-to-show});
  font-size: $font-size;
  line-height: $line-height;
  overflow: hidden;
}

And now for the ellipsis ... a bit of vanilla JS (we split the text to be clamped by words and remove those words that are out of visible text area boundaries. In the end the ellipsis got appended.

 function ellipsizeTextElement(element) {
    var nodeList = document.querySelectorAll(element);
    var elements = Array.prototype.slice.call(nodeList, 0);
    elements.forEach(function(element) {
      var wordArray = element.innerHTML.split(' ');
      while (element.scrollHeight > element.offsetHeight) {
        wordArray.pop();
        element.innerHTML = wordArray.join(' ') + '...';
      }
    });
  }

BONUS 💡
Why limiting ourselves to 3 lines when we can tweak that from HTML (let's say we have different sections of cards and we want each to have different line clampin' rules)
We can easily add 'lines-X' class to our element that is to be clamped. Then we need some more SCSS (I restricted the number of lines from 1 to 6 and applied this to all the headings for my project' purposes):

$heading-font-sizes: (h1: $h1-font-size, h2: $h2-font-size, h3: $h3-font-size, h4: $h4-font-size, h5: $h5-font-size, h6: $h6-font-size );

@for $lines from 1 to 6 {
  h#{$lines} {
    $headingFontSize: map-get($heading-font-sizes, h#{$lines} );

    @for $lines from 1 to 6 {
      &.ellipsize-element.lines-#{$lines} {
        @include limitTextToLineNumbersMixin( $headingFontSize, 1.5, #{$lines})
      }
    }
  }
}

Have a better approach 🤓? I'm all ears 👂. Happy coding guys and gals 🙌

Top comments (4)

Collapse
 
ekeijl profile image
Edwin

You would expect that CSS would have a solution for this by now. 😅
If you want pure CSS, I would use a linear gradient overlay as such:

The advantage here is that all the content is still there and you only need to apply a single CSS class.

You should totally add a live example of your code btw!

Collapse
 
emilcheva profile image
Evgenia Milcheva • Edited

Good try (I like the effort you spent to make it CSS only) but has some cons to me.

  1. You won't be able to add ellipsis this way (this kind of changes the look I'm after)
  2. Increasing the line-height practically changes the number of lines to be shown (not in my case though as line-height is part of the mixin calc)

P.S. Accepting your remark and will add a codepen demonstrating my approach. Thank you!

Collapse
 
pfacklam profile image
Paul Facklam

Love this article.

Collapse
 
trandaison profile image
Son Tran

This will not work on japanese text, there is no spaces between words in japanese (and chinese, korean,...)