`overflow-hidden` 文本截断判断技巧

271 阅读3分钟

传统解决方案通常通过比较元素的 scrollWidthclientWidth 来判断文本是否被截断。

此外,我们可以使用 Range 的方式更精确地判断文本是否被截断。

  • overflow: hidden 在布局上会将文本进行截断,但是双击全选复制的时候,可以复制到全部的内容。因此我们可以基于此特性,通过 浏览器提供的 Range api 获取 文本的宽度/高度进行判断。

    	 
      function getPadding(el) { const style = window.getComputedStyle(el, null) 
          const parseInt = (number) => Number.parseInt(number) || 0 
          const left = parseInt(style.paddingLeft) 
          const right = parseInt(style.paddingRight) 
          const top = parseInt(style.paddingTop) 
          const bottom = parseInt(style.paddingBottom) 
          return { left, right, top, bottom } 
      }
      const app = document.getElementById('app')
      
      const range = document.createRange()
      range.setStart(app, 0)
      range.setEnd(app, app.childNodes.length)
      
      const { width: rangeWidth, height: rangeHeight } =
        range.getBoundingClientRect()
      
      const { width, height } = target.getBoundingClientRect()
    
      const { left, top, bottom, right } = getPadding(target)
      const verPadding = top + bottom 
      const horPadding = left + right;
    
      if (rangeHeight + verPadding > height || rangeWidth + horPadding > width) {
    	// 文本被截断 执行的逻辑
      }
    

何时使用 Range

正常布局下,Range 都可以使用 scrollWidth 的方式平替。

当布局出现异常布局偏移时,scrollWidth 可能无法准确判断文本是否被截断。

  • 布局偏移:正常文字的排列方向都是从左往右,右区域超出的部分被 hidden 截断,此时的 scrollWidth 会包括 hidden 的部分宽度,如果使用 css 的一些属性使得文字排列从左开始就已经被截断了一部分,最开始截断的部分是不会算在 scrollWidth 中。简单总结:如果文本排列是从左到右布局,右边被截断的部分会算在 scrollWidth 中,而左边被截断的部分不会。
  • 如果元素从左往右布局,则向左偏移被称之为左向异常布局偏移,向右偏移称之为右向正常布局偏移

举个例子:如果元素使用了 text-indent: -50px; 的方式进行了负缩进而被隐藏了。

.text-container {
      width: 200px;
      overflow: hidden;
      border: 1px solid #000;
}
.text-container p {
      display: inline-block;
      text-indent: -50px; /* 负缩进 */
      white-space: nowrap;
}
<div class="text-container" id="text">
    <p>这是一个带有负缩进的长文本。</p>
  </div>
  
  <button onclick="checkScrollWidth()">使用 scrollWidth 检测</button>
  <button onclick="checkRange()">使用 createRange 检测</button>
 function checkScrollWidth() {
      const textElement = document.getElementById('text');
      if (textElement.scrollWidth > textElement.clientWidth) {
        console.log('文本被截断了 (scrollWidth 检测)');
      } else {
        console.log('文本未被截断 (scrollWidth 检测)');
      }
    }

    function checkRange() {
      const textElement = document.getElementById('text');
      const range = document.createRange();
       range.setStart(textElement, 0)
       range.setEnd(textElement, textElement.childNodes.length) // 选择 p 标签内容

      const rangeWidth = range.getBoundingClientRect().width;
      if (rangeWidth > textElement.clientWidth) {
        console.log('文本被截断了 (createRange 检测)');
      } else {
        console.log('文本未被截断 (createRange 检测)');
      }
    }

在线体验:

此时两者的判断结果是有差异的,由于布局向左偏移了 50px ,没有造成滚动条,因此scrollWidth 的方式判断为文本没有截断。而range则包含了左边截断的部分的宽度。

何时使用 scrollwidth

如果元素使用 transform 或者定位使得元素超出了父元素的边界时(前提是向右正常布局偏移的情况)。 上述的 range 方法将再难以捕获边界情况。

举个例子:

此时的 range 的范围始终是小于父元素的宽度的,但是由于向右的布局偏移,此时的 scrollWidth 包括了被截断元素的宽度,因此可以使用 scrollWidth 进行一个判断。

 <div style="position: relative; padding:0 20px;height: 30px; width: 80px; overflow:hidden;text-overflow: ellipsis;">
   <div style="position: absolute;right: -5px;">
      foobaz
   </div>
</div>
  • 在线体验:

Range 与 ScrollWidth 的巧妙结合

RangeScrollWidth 结合,无论布局是否是异常偏移,都能精准判断出 overflow 的边界情况。

具体修改如下:

function getPadding(el) {
  const style = window.getComputedStyle(el, null)
  const parseInt = (number) => Number.parseInt(number) || 0

  const left = parseInt(style.paddingLeft)
  const right = parseInt(style.paddingRight)
  const top = parseInt(style.paddingTop)
  const bottom = parseInt(style.paddingBottom)
  return { left, right, top, bottom }
}

  const app = document.getElementById('app')

  const range = document.createRange()
  range.setStart(app, 0)
  range.setEnd(app, app.childNodes.length)

  const { width: rangeWidth, height: rangeHeight } =
    range.getBoundingClientRect()
    
  const { left, top, bottom, right } = getPadding(target)

  const verPadding = top + bottom;
  const horPadding = left + right; 

  const { width, height } = target.getBoundingClientRect()
  if (rangeHeight + verPadding > height || 
      rangeWidth + horPadding > width
+     target.scrollWidth > target.clientWidth      
      ) {
   // overflow-hidden is truly
  }