<!--
pass to the ".truncate" element a "style" attribute
with "--line-clamp" desired lines to clamp
-->
<div class='truncate' contenteditable spellcheck='false'>
The <em><code>-webkit-line-clamp</code></em> CSS property allows limiting of the contents of a block container to the specified number of lines.
It <span>only</span> works in combination with the display property set to -webkit-box or -webkit-inline-box and the -webkit-box-orient property set to vertical.
In most cases you will also want to set overflow to hidden, otherwise the contents won't be clipped but an <span>ellipsis</span> will still be shown after the specified number of lines.
</div>
.truncate{
display: -webkit-box;
-webkit-line-clamp: var(--line-clamp, 1);
-webkit-box-orient: vertical;
word-break: var(--word-break, 'none');
overflow: hidden;
hyphens: auto;
text-align: var(--align, left);
// Automatically use "word-break: break-all" for single-lines
// (https://css-tricks.com/css-switch-case-conditions)
--is-single-line: 1 - Clamp(0, Calc(var(--line-clamp) - 1), var(--line-clamp));
--delay: Calc(-1s * (var(--is-single-line, 1) - 1));
animation: states 1s var(--delay) paused;
@keyframes states {
0% { word-break: break-all; }
}
}
/**** JUST FOR THIS DEMO: ****/
html,body{ height:100%; }
body{
--bg: #4A90E2;
background: var(--bg);
height: 100vh;
display: grid;
place-items: center;
font: calc(1vw + 1.2em)/1.4 Roboto, Arial;
color: white;
}
.truncate{
font-size: var(--font-size, 1em);
width: var(--width, 50%);
text-shadow: 1px 3px 0 rgba(black, .3);
position: relative;
background: inherit;
&:hover{
outline: 2px dotted #FFFFFF99;
}
// fade text's last word, if clamped ()
&::after{
content: '';
width: 0;
height: 2ch;
position: absolute;
bottom: 0;
right: 0;
pointer-events: none;
//background: linear-gradient(90deg, transparent, var(--bg));
background: inherit; // it's easier to work with inheritance
mask-image: linear-gradient(to left, black, transparent);
transition: .2s;
}
&.clamped{
&::after{
width: 3em;
}
}
> span{
text-decoration: underline;
}
}
View Compiled
// Top-right controls
const elm = document.querySelector('.truncate')
let debounce;
const myKnobs = new Knobs({
CSSVarTarget: elm,
visible: 2,
knobs: [
{
cssVar: ['line-clamp'],
label: 'Lines to clamp',
type: 'range',
value: 2,
min: 1,
max: 10,
step: 1,
onChange: checkTruncated
},
{
cssVar: ['width', '%'],
label: 'Width',
type: 'range',
value: 50,
min: 10,
max: 100,
step: 1,
onChange: checkTruncated
},
{
cssVar: ['font-size', 'em'],
label: 'Font size',
type: 'range',
value: 1,
min: .6,
max: 1.5,
step: .1,
onChange: checkTruncated
},
// break-all allows a better truncating for a single-line clamps,
// so the text will fit best within the container
/*
{
cssVar: ['word-break'],
label: 'Word Break',
type: 'checkbox',
labelTitle: 'will automatically be "breal-all" for single-lines',
checked: true,
value: 'break-all'
},
*/
{
cssVar: ['align'],
label: 'Align text',
type: 'radio',
name: 'align-radio-group',
options: [
{ value:'left', hidden:true, label: '<svg viewBox="0 0 28 28"><path d="M28 21v2c0 0.547-0.453 1-1 1h-26c-0.547 0-1-0.453-1-1v-2c0-0.547 0.453-1 1-1h26c0.547 0 1 0.453 1 1zM22 15v2c0 0.547-0.453 1-1 1h-20c-0.547 0-1-0.453-1-1v-2c0-0.547 0.453-1 1-1h20c0.547 0 1 0.453 1 1zM26 9v2c0 0.547-0.453 1-1 1h-24c-0.547 0-1-0.453-1-1v-2c0-0.547 0.453-1 1-1h24c0.547 0 1 0.453 1 1zM20 3v2c0 0.547-0.453 1-1 1h-18c-0.547 0-1-0.453-1-1v-2c0-0.547 0.453-1 1-1h18c0.547 0 1 0.453 1 1z"></path></svg>' },
{ value:'center', hidden:true, label:'<svg viewBox="0 0 1024 1024"><path d="M1024 768v73.143c0 20-16.571 36.571-36.571 36.571h-950.857c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h950.857c20 0 36.571 16.571 36.571 36.571zM804.571 548.571v73.143c0 20-16.571 36.571-36.571 36.571h-512c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h512c20 0 36.571 16.571 36.571 36.571zM950.857 329.143v73.143c0 20-16.571 36.571-36.571 36.571h-804.571c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h804.571c20 0 36.571 16.571 36.571 36.571zM731.429 109.714v73.143c0 20-16.571 36.571-36.571 36.571h-365.714c-20 0-36.571-16.571-36.571-36.571v-73.143c0-20 16.571-36.571 36.571-36.571h365.714c20 0 36.571 16.571 36.571 36.571z"></path></svg>' },
{ value:'right', hidden:true, label:'<svg viewBox="0 0 28 28"><path d="M28 21v2c0 0.547-0.453 1-1 1h-26c-0.547 0-1-0.453-1-1v-2c0-0.547 0.453-1 1-1h26c0.547 0 1 0.453 1 1zM28 15v2c0 0.547-0.453 1-1 1h-20c-0.547 0-1-0.453-1-1v-2c0-0.547 0.453-1 1-1h20c0.547 0 1 0.453 1 1zM28 9v2c0 0.547-0.453 1-1 1h-24c-0.547 0-1-0.453-1-1v-2c0-0.547 0.453-1 1-1h24c0.547 0 1 0.453 1 1zM28 3v2c0 0.547-0.453 1-1 1h-18c-0.547 0-1-0.453-1-1v-2c0-0.547 0.453-1 1-1h18c0.547 0 1 0.453 1 1z"></path></svg>' }
],
value: 'left',
defaultValue: 'left'
},
"Readonly:",
{
label: 'Truncated detected',
type: 'checkbox',
disabled: true,
checked: true,
id: 'isTruncatedToggle'
},
]
})
const isTextClamped = elm => elm.scrollHeight > elm.clientHeight
function checkTruncated(){
clearTimeout(debounce)
debounce = setTimeout(() => {
const clamped = isTextClamped(elm)
myKnobs.DOM.form.querySelector('#isTruncatedToggle').checked = clamped
elm.classList.toggle('clamped', clamped)
}
, 100)
}
This Pen doesn't use any external CSS resources.