Fixed number of lines
Here's the CSS required to make a paragraph truncate with an ellipsis after a specific number of lines (the vendor prefixes are required):
.line-clamp {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 5;
overflow: hidden;
}
Here is the above CSS in action. Try adjusting the paragraph's width:
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Dolor sit amet consectetur adipiscing elit duis tristique sollicitudin. Suscipit adipiscing bibendum est ultricies integer quis auctor. Odio facilisis mauris sit amet massa vitae tortor condimentum lacinia. Nam libero justo laoreet sit amet cursus sit amet. Nisl vel pretium lectus quam id. Proin nibh nisl condimentum id. Sagittis vitae et leo duis ut diam quam nulla. Sed cras ornare arcu dui vivamus arcu felis. Vulputate ut pharetra sit amet aliquam id diam. Id neque aliquam vestibulum morbi. Tellus at urna condimentum mattis. Non enim praesent elementum facilisis leo vel. Turpis massa tincidunt dui ut. Risus quis varius quam quisque id. Amet purus gravida quis blandit. Felis eget velit aliquet sagittis id consectetur purus ut. Quis commodo odio aenean sed adipiscing. Pellentesque elit eget gravida cum sociis natoque penatibus et magnis. Facilisis leo vel fringilla est ullamcorper eget nulla. Montes nascetur ridiculus mus mauris. Nunc sed augue lacus viverra vitae congue. Morbi non arcu risus quis varius quam quisque id. Adipiscing tristique risus nec feugiat in fermentum posuere urna nec. Elit ullamcorper dignissim cras tincidunt lobortis feugiat vivamus.
Dynamic number of lines
You can also use a CSS variable and some JavaScript to dynamically set the -webkit-line-clamp
property based on the container's height. Using this CSS:
.dynamic-line-clamp {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: var(--lines);
overflow: hidden;
&::after{
height: 1lh;
}
}
And this JavaScript:
const elem = document.querySelector('.dynamic-line-clamp');
const lh = Number(getComputedStyle(elem, '::after').getPropertyValue('height').slice(0, -2));
new ResizeObserver(() => {
const lines = Math.round(elem.clientHeight / lh);
elem.style.setProperty('--lines', lines);
}).observe(elem);
Try adjusting the height:
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Dolor sit amet consectetur adipiscing elit duis tristique sollicitudin. Suscipit adipiscing bibendum est ultricies integer quis auctor. Odio facilisis mauris sit amet massa vitae tortor condimentum lacinia. Nam libero justo laoreet sit amet cursus sit amet. Nisl vel pretium lectus quam id. Proin nibh nisl condimentum id. Sagittis vitae et leo duis ut diam quam nulla. Sed cras ornare arcu dui vivamus arcu felis. Vulputate ut pharetra sit amet aliquam id diam. Id neque aliquam vestibulum morbi. Tellus at urna condimentum mattis. Non enim praesent elementum facilisis leo vel. Turpis massa tincidunt dui ut. Risus quis varius quam quisque id. Amet purus gravida quis blandit. Felis eget velit aliquet sagittis id consectetur purus ut. Quis commodo odio aenean sed adipiscing. Pellentesque elit eget gravida cum sociis natoque penatibus et magnis. Facilisis leo vel fringilla est ullamcorper eget nulla. Montes nascetur ridiculus mus mauris. Nunc sed augue lacus viverra vitae congue. Morbi non arcu risus quis varius quam quisque id. Adipiscing tristique risus nec feugiat in fermentum posuere urna nec. Elit ullamcorper dignissim cras tincidunt lobortis feugiat vivamus.
Note that overflow: hidden
will only hide text that is outside the
container element. So if you resize the paragraph to cut through the middle of a line, the ellipsis will appear on
the line above it but the cut-off text will still be visible.
Dynamic number of lines: quality of life improvements
If your paragraph's height is dynamic, it's useful to add some JavaScript to snap the paragraph's height to a multiple of
its line-height
whenever the paragraph is resized. To do so, update ResizeObserver
:
new ResizeObserver(() => {
const lines = Math.round(elem.clientHeight / lh);
elem.style.setProperty('--lines', lines);
elem.style.setProperty('height', `${lines * lh}px`);
}).observe(elem);
It's also best to debounce the observer for better performance and to prevent the observer from fighting with the user for control over the element's height while the user is resizing it. Using this basic debounce function:
function debounce(fn, wait) {
let timeoutId;
return () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(fn, wait);
}
}
You can update the observer to cancel if another event is fired within 200 milliseconds:
new ResizeObserver(debounce(() => {
const lines = Math.round(elem.clientHeight / lh);
elem.style.setProperty('--lines', lines);
elem.style.setProperty('height', `${lines * lh}px`);
}, 200)).observe(elem);
Full example
Here's the final CSS:
.dynamic-line-clamp {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: var(--lines);
overflow: hidden;
&::after{
height: 1lh;
}
}
And the final JavaScript:
function debounce(fn, wait) {
let timeoutId;
return () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(fn, wait);
}
}
const elem = document.querySelector('.dynamic-line-clamp');
const lh = Number(getComputedStyle(elem, '::after').getPropertyValue('height').slice(0, -2));
new ResizeObserver(debounce(() => {
const lines = Math.round(elem.clientHeight / lh);
elem.style.setProperty('--lines', lines);
elem.style.setProperty('height', `${lines * lh}px`);
}, 200)).observe(elem);
And here it is in action!
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Dolor sit amet consectetur adipiscing elit duis tristique sollicitudin. Suscipit adipiscing bibendum est ultricies integer quis auctor. Odio facilisis mauris sit amet massa vitae tortor condimentum lacinia. Nam libero justo laoreet sit amet cursus sit amet. Nisl vel pretium lectus quam id. Proin nibh nisl condimentum id. Sagittis vitae et leo duis ut diam quam nulla. Sed cras ornare arcu dui vivamus arcu felis. Vulputate ut pharetra sit amet aliquam id diam. Id neque aliquam vestibulum morbi. Tellus at urna condimentum mattis. Non enim praesent elementum facilisis leo vel. Turpis massa tincidunt dui ut. Risus quis varius quam quisque id. Amet purus gravida quis blandit. Felis eget velit aliquet sagittis id consectetur purus ut. Quis commodo odio aenean sed adipiscing. Pellentesque elit eget gravida cum sociis natoque penatibus et magnis. Facilisis leo vel fringilla est ullamcorper eget nulla. Montes nascetur ridiculus mus mauris. Nunc sed augue lacus viverra vitae congue. Morbi non arcu risus quis varius quam quisque id. Adipiscing tristique risus nec feugiat in fermentum posuere urna nec. Elit ullamcorper dignissim cras tincidunt lobortis feugiat vivamus.