Below I've written some code that takes some content in a table cell and truncates it to two lines if it runs over. When trying to find the correct content length that is close to 2 full lines (but does not go over!) I take a logarithmic approach (I think). I first cut the content in half. I then check the content again and either add or subtract a quarter (half of the half). Etc.
Requirements:
- Ellipsis (...) at the end of truncated text.
- Responsive, strategy should work for dynamic width cells
Questions:
- In the snippet, I've included an example that results in 3 lines. How can I guarantee I land at 2 lines while getting reasonably close to 2 full lines?
- I did the logarithmic approach so I wouldn't have to do something like pop a word, retest, pop a word, retest, etc. This still seems too expensive, how can I improve this?
document.querySelectorAll('.expand').forEach(td => {
  
  // get cell styles
  let styles = window.getComputedStyle(td);
  let lineHeight = parseInt(styles.lineHeight, 10);
  // create test element, mostly because td doesn't support max-height
  let el = document.createElement('div');
  el.innerHTML = td.innerHTML;
  el.style.maxHeight = (lineHeight * 2) + 'px';
  el.style.overflow = 'hidden';
  td.appendChild(el);
  // if scrollHeight is greater than clientHeight, we need to do some expand-y stuff
  if (el.scrollHeight > el.clientHeight) {
    
    // store content
    let content = el.innerHTML.trim(), 
        len = content.length;
    for (let i=Math.round(len*.5);; i=Math.round(i*.5)) {
      let over = el.scrollHeight > el.clientHeight;
      
      // if over 2 lines, cut by half
      // else increase by half
      over ? (len-=i) : (len+=i);
      
      // update innerHTML with updated content
      el.innerHTML = content.slice(0, len);
      
      console.log(i, len);
      
      // break if within margin of 10 and we landed under
      if (i<10 && !over) break;
    }
    
    td.innerHTML = `
      <div class="hide-expanded">${el.innerHTML.slice(0, -3).trim()}...</div>
      <div class="show-expanded">${content}</div>
      <button type="button">Toggle</button>`;
    
    td.querySelector('button').addEventListener('click', e => td.classList.toggle('expanded'))
  }
});html {
  font-size: 14px;
  line-height: 24px;
  font-family: Helvetica, Arial, sans-serif;
}
table {
  border-collapse: collapse;
}
td {
  white-space: nowrap;
  padding: 1rem;
}
.expand {
  white-space: normal;
}
.expand:not(.expanded) .show-expanded,
.expand.expanded .hide-expanded {
  display: none;
}<table>
  <tbody>
    <tr>
      <td class="expand">This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content. This is some content.</td>
    </tr>
  </tbody>
</table>