Benito Serna
Ruby, Rails, TDD...

A "read-more" behavior truncating by number of lines with css line-clamp and Stimulus.js

I have already shared a way of implementing a “read-more” behavior truncating by the number of lines instead of the number of words.

But now I want to share how you can do it using the line-clamp css property.

The example

Visit example page

The html and css

Your are going to need…

html { line-height: 1.5 }
.hide { display: none; }
.line-clamp {
  overflow : hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-box-orient: vertical;
}
<div data-controller="read-more"
     data-read-more-lines-value="3"
     data-read-more-hide-class="hide"
     data-read-more-truncate-class="line-clamp"
     data-action="resize@window->read-more#render">
  <p data-read-more-target="content">
    The content that you want to truncate...
  </p>
  <button class="hide"
    data-read-more-target="moreButton"
    data-action="read-more#showMore">
    Show more
  </button>
  <button class="hide"
    data-read-more-target="lessButton"
    data-action="read-more#showLess">
    Show less
  </button>
</div>

The javascript

You are going to use line-clamp to truncate the content, assigning the configured linesValue to the property -webkit-line-clamp.

But you are going to do it just if the height is greater that the expectedHeigt that is the product of the linesValue with the lineHeight.

class ReadMoreController extends Stimulus.Controller {
  static get targets() {
    return ["content", "moreButton", "lessButton"]
  }

  static get classes() {
    return ["truncate", "hide"]
  }

  static get values() {
    return { lines: Number }
  }

  connect() {
    this.render()
  }

  render() {
    this.showAllContent()

    if (this.height() > this.expectedHeight()) {
      this.showLess()
    } else {
      this.showAllConent()
      this.hide(this.moreButtonTarget);
      this.hide(this.lessButtonTarget);
    }
  }

  showMore() {
    this.showAllContent();
    this.hide(this.moreButtonTarget);
    this.show(this.lessButtonTarget);
  }

  showLess() {
    this.truncateContent();
    this.hide(this.lessButtonTarget);
    this.show(this.moreButtonTarget);
  }

  showAllContent() {
    this.contentTarget.classList.remove(this.truncateClass);
  }

  truncateContent() {
    this.contentTarget.style["-webkit-line-clamp"] = this.linesValue;
    this.contentTarget.classList.add(this.truncateClass);
  }

  show(target) {
    target.classList.remove(this.hideClass)
  }

  hide(target) {
    target.classList.add(this.hideClass)
  }

  lineHeight() {
    let style = window.getComputedStyle(this.contentTarget)
    return parseFloat(style.lineHeight, 10);
  }

  height() {
    return this.contentTarget.offsetHeight;
  }

  expectedHeight() {
    return this.linesValue * this.lineHeight();
  }
}

Other ways to do it…

If for some reason, you can’t use line-clamp, maybe you can try doing the truncation with custom javascript.

And if you can truncate by number of characters, you can check this example using the “truncate” helper.