text-clamping

by Richard Hunter

HTML

<div id="foo" class="container">
  
  It's the end of the world as we know it and I feel fine. It's the end of the world It's the end of the world as we know it and I feel fine. It's the end of the world It's the end of the world as we know it and I feel fine. It's the end of the world as we know it.d It's the end of the world as we know it and I feel fine. It's the end of the world It's the end of the world as we know it and I feel fine. It's the end of the world as we know it.
</div>


<h2>Usage</h2>
<p>Good practice is to set the max-height property of the element and its overflow to hidden in the stylesheet to prevent an unsightly flash of unclipped content. </p>

SCSS

.container {
  background: aquamarine;
  display: block;
  width: 50%;
  font-size: 12px;
  line-height: 1.2;
  overflow: hidden;
  max-height: calc(1.2 * 12px * 3);
}

JavaScript

function outputsize(el, maxLines) {
  let sourceText = el.textContent;

  return function() {
    let text = sourceText;
    
    const width = el.offsetWidth;
    const styles = window.getComputedStyle(el);

    const lineHeight = styles.getPropertyValue('line-height');
    const fontSize = styles.getPropertyValue('font-size');

    const dummyEl = document.createElement('div');
    
    // hide dummy el offscreen
    dummyEl.style.position = 'absolute';
    dummyEl.style.left = '-999px';
    dummyEl.style.opacity = 0;
    
    document.body.append(dummyEl);

    dummyEl.style.fontSize = fontSize;
    dummyEl.style.lineHeight = lineHeight;
    dummyEl.style.width = width + 'px';
    dummyEl.textContent = text;

    const targetHeight = maxLines * parseFloat(lineHeight);

    if (dummyEl.offsetHeight > targetHeight) {
    
    	/*  not sure how performant this is. Could slice off more
       *  characters at a time, and/or estimate a starting point close to the
       *  result.
       */
       
      while (dummyEl.offsetHeight > targetHeight) {
        text = text.slice(0, -1);
        dummyEl.textContent = text;
      }

      el.textContent = text.slice(0, -3).trim() + '...';
    }

    dummyEl.remove();
  }
}

let element = document.getElementById('foo');
let maxLines = 3;
new ResizeObserver(outputsize(element, maxLines)).observe(element)