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#showMore
click->read-more#showLess
html { 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.