Are you looking for a way of implementing a “read-more” behavior truncating by the number of lines instead of the number of words?
If this is your case, this article can help you =) …
Your are going to need…
linesValue to configure the number of lines that we want to display,content, the moreButton and the lessButton…resize@window->read-more#render - to calculate the truncation on rezise.click->read-more#showMoreclick->read-more#showLesshtml { line-height: 1.5 }
.hide { display: none; }
<div data-controller="read-more"
data-read-more-lines-value="3"
data-read-more-hide-class="hide"
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>
On render you are going to truncate the content, to the configured linesValue.
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 ["hide"]
}
static get values() {
return { lines: Number }
}
connect() {
this.content = this.contentTarget.textContent;
this.render()
}
render() {
this.showAllContent()
if (this.height() > this.expectedHeight()) {
this.showLess()
} else {
this.showAllContent()
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.removeContent();
this.wordsList().forEach((word) => this.addWordToContent(word))
}
truncateContent() {
this.calculateWordsToDisplayWhenTruncated();
this.renderTrucatedContentWithEllipsis();
}
calculateWordsToDisplayWhenTruncated() {
this.wordsToDisplayWhenTrucated = [];
this.removeContent();
this.wordsList().forEach((word) => {
if (this.height() < this.expectedHeight()) {
this.wordsToDisplayWhenTrucated.push(word)
this.addWordToContent(word)
}
})
}
renderTrucatedContentWithEllipsis() {
this.wordsToDisplayWhenTrucated.pop()
this.removeContent();
this.wordsToDisplayWhenTrucated.forEach((word) => this.addWordToContent(word))
this.addToContent("...")
if (this.height() > this.expectedHeight()) {
this.renderTrucatedContentWithEllipsis()
}
}
show(target) {
target.classList.remove(this.hideClass)
}
hide(target) {
target.classList.add(this.hideClass)
}
removeContent() {
this.contentTarget.textContent = "";
}
addWordToContent(word) {
this.addToContent(" " + word);
}
addToContent(text) {
this.contentTarget.textContent += text
}
lineHeight() {
let style = window.getComputedStyle(this.contentTarget)
return parseFloat(style.lineHeight, 10);
}
height() {
return this.contentTarget.offsetHeight;
}
expectedHeight() {
return this.linesValue * this.lineHeight();
}
wordsList() {
return this.content.split(" ")
}
}
If you are looking for a way to do the truncation using css line-clamp, you take a look to this example using “line-clamp”.
And if you can truncate by number of characters, you can check this example using the “truncate” helper.
Here I try to share knowledge and fixes to common problems and struggles for ruby on rails developers, like How to fetch the latest-N-of-each record or How to test that an specific mail was sent or a Capybara cheatsheet. You can see more examples on Most recent posts or all posts.