传统解决方案通常通过比较元素的 scrollWidth
与 clientWidth
来判断文本是否被截断。
此外,我们可以使用 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 的巧妙结合
将 Range
与 ScrollWidth
结合,无论布局是否是异常偏移,都能精准判断出 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
}