如何使用Javascript检查文本是否被CSS截断



我正在尝试使用JS检测文本是否被截断。这里提到的解决方案效果很好,除了下面的边缘情况。正如您将注意到的,如果文本在视觉上被截断,鼠标悬停时的第一个块将返回 false。

function isEllipsisActive(e) {
return (e.offsetWidth < e.scrollWidth);
}
function onMouseHover(e) {
console.log(`is truncated: ${isEllipsisActive(e)}`);
}
div.red {
margin-bottom: 1em;
background: red;
color: #fff;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 300px;
cursor: pointer;
}
<h6>Hover mouse and watch for console messages.</h6>
<!-- should return true -->
<div class="red" onmouseover="onMouseHover(this)">
<a>Analytics reports comes through garbled. Plsss</a>
</div>
<!-- should return true -->
<div class="red" onmouseover="onMouseHover(this)">
<a>Analytics reports comes through garbled. Plsssssss</a>
</div>
<!-- should return false -->
<div class="red" onmouseover="onMouseHover(this)">
<a>Normal text</a>
</div>

我追求的解决方案是每当文本被 css 截断时,函数都返回 true。

这里的问题是HTMLElement.offsetWidthElement.scrollWidth都是舍入值.
你的元素的真实内宽实际上在我的电脑上300.40625px,这在我的Chrome中被300px了。

这里的解决方案是使用返回浮点值的 API,并且没有太多......

人们可能会试图检查内部<a>getBoundingClientRect().width,这实际上适用于所有OP的情况,但这仅适用于这些情况:向div添加填充,为这些<a>添加边距,或其他元素,它就被破坏了。

document.querySelectorAll( ".test" ).forEach( el => {
el.classList.toggle( "truncated", isEllipsisActive( el ) );
} );
function isEllipsisActive( el ) {
return el.firstElementChild.getBoundingClientRect().width > el.getBoundingClientRect().width;
}
div.test {
margin-bottom: 1em;
background: red;
color: #fff;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 300px;
}
div.truncated {
background: green;
}
.margin-left {
margin-left: 225px;
}
<!-- should be green -->
<div class="test">
<a>Analytics reports comes through garbled. Plsss</a>
</div>
<!-- should be green -->
<div class="test">
<a>Analytics reports comes through garbled. Plsssssss</a>
</div>
<!-- should be green -->
<div class="test">
<a>Analytics</a><a> reports comes through garbled. Plsssssss</a>
</div>
<!-- should be green -->
<div class="test">
<a class="margin-left">Shorter text</a>
</div>
<!-- should be red -->
<div class="test">
<a>Normal text</a>
</div>

因此,人们可能会认为 Range 及其getBoundingClientRect()方法就可以了,但是,虽然这能够告诉元素中文本内容的实际大小,但这只会检查文本内容。如果滚动是由边距引起的,则它不起作用。

document.querySelectorAll(".test").forEach( el => {
el.classList.toggle( "truncated", isEllipsisActive( el ) );
} );
function isEllipsisActive( el ) {
return el.scrollWidth !== el.offsetWidth ?
el.scrollWidth > el.offsetWidth :
checkRanges( el ); // Blink and Webkit browsers do floor scrollWidth
}
function checkRanges( el ) {
const range = new Range();
range.selectNodeContents( el );

const range_rect = range.getBoundingClientRect();
const el_rect = el.getBoundingClientRect();
// assumes ltr direction
return range_rect.right > el_rect.right;
}
div.test {
margin-bottom: 1em;
background: red;
color: #fff;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 300px;
}
div.truncated {
background: green;
}
.margin-left {
margin-left: 225px;
}
.margin-right {
margin-right: 225px;
}
<!-- should be green -->
<div class="test">
<a>Analytics reports comes through garbled. Plsss</a>
</div>
<!-- should be green -->
<div class="test">
<a>Analytics reports comes through garbled. Plsssssss</a>
</div>
<!-- should be green -->
<div class="test">
<a>Analytics</a><a> reports comes through garbled. Plsssssss</a>
</div>
<!-- should be green -->
<div class="test">
<a class="margin-left">Shorter text</a>
</div>
<!-- should be green -->
<div class="test">
<a class="margin-right">Shorter text</a>
</div>
<!-- should be red -->
<div class="test">
<a>Normal text</a>
</div>

因此,我能想到的唯一解决方案依赖于Chrome特定的行为:它们确实在Range.getClientRects().
的结果中公开了渲染省略号的客户端矩形,因此确定的方法,在Chrome中,省略号是否被渲染,是切换text-overflow属性并检查是否出现了这个DOMRect。

但是,由于这是仅限Chrome的行为,因此我们仍然需要检查Safari的范围边界框位置。

document.querySelectorAll(".test").forEach( el => {
el.classList.toggle( "truncated", isEllipsisActive( el ) );
} );
function isEllipsisActive( el ) {
return el.scrollWidth !== el.offsetWidth ?
el.scrollWidth > el.offsetWidth :
checkRanges( el ); // Blink and Webkit browsers do floor scrollWidth
}
function checkRanges( el ) {
const range = new Range();
range.selectNodeContents( el );

const range_rect = range.getBoundingClientRect();
const el_rect = el.getBoundingClientRect();
// assumes ltr direction
if( range_rect.right > el_rect.right ) {
return true;
}
// Following check would be enough for Blink browsers
// but they are the only ones exposing this behavior.

// first force ellipsis
el.classList.add( "text-overflow-ellipsis" );
// get all the client rects (there should be one for the ellipsis)
const rects_ellipsis = range.getClientRects();
// force no ellipsis
el.classList.add( "text-overflow-clip" );
const rects_clipped = range.getClientRects();
// clean
el.classList.remove( "text-overflow-ellipsis" );
el.classList.remove( "text-overflow-clip" );
// if the counts changed, the text is truncated
return rects_clipped.length !== rects_ellipsis.length;
}
/* 2 new clasess to force the rendering of ellipsis */
.text-overflow-ellipsis {
text-overflow: ellipsis !important;
}
.text-overflow-clip {
text-overflow: clip !important;
}
div.test {
margin-bottom: 1em;
background: red;
color: #fff;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 300px;
}
div.truncated {
background: green;
}
.margin-left {
margin-left: 225px;
}
.margin-right {
margin-right: 225px;
}
<!-- should be green -->
<div class="test">
<a>Analytics reports comes through garbled. Plsss</a>
</div>
<!-- should be green -->
<div class="test">
<a>Analytics reports comes through garbled. Plsssssss</a>
</div>
<!-- should be green -->
<div class="test">
<a>Analytics</a><a> reports comes through garbled. Plsssssss</a>
</div>
<!-- should be green -->
<div class="test">
<a class="margin-left">Shorter text</a>
</div>
<!-- should be green -->
<div class="test">
<a class="margin-right">Shorter text</a>
</div>
<!-- should be red -->
<div class="test">
<a>Normal text</a>
</div>

<小时 />

小更新

由于此 CL Chrome 在起始范围为0的情况下不会公开省略号的边界框,(显然在上述代码片段的倒数第二个测试中就是这种情况).
这意味着我们的解决方法在这种特殊情况下不再有效。

尝试使用

function isEllipsisActive(e) {
var c = e.cloneNode(true);
c.style.display = 'inline';
c.style.width = 'auto';
c.style.visibility = 'hidden';
document.body.appendChild(c);
const truncated = c.offsetWidth >= e.clientWidth;
c.remove();
return truncated;
}

这很笨拙,但它有效。

Kaiido 一针见血地提到问题在于offsetWidthscrollWidth反映舍入值,而省略号是基于浮点值显示的。 但他无法找到合适的跨浏览器解决方案。

但是,将这些知识与 see sharper 方法的修改版本相结合在我的测试中非常有效,并且应该是可靠的和跨浏览器的。

function isEllipsisActive(e) {
const temp = e.cloneNode(true);
temp.style.position = "fixed";
temp.style.overflow = "visible";
temp.style.whiteSpace = "nowrap";
temp.style.visibility = "hidden";
e.parentElement.appendChild(temp);
try {
const fullWidth = temp.getBoundingClientRect().width;
const displayWidth = e.getBoundingClientRect().width;
return fullWidth > displayWidth;
} finally {
temp.remove();
}
}

function isEllipsisActive(e) {
const temp = e.cloneNode(true);
temp.style.position = "fixed";
temp.style.overflow = "visible";
temp.style.whiteSpace = "nowrap";
temp.style.visibility = "hidden";
e.parentElement.appendChild(temp);
try {
const fullWidth = temp.getBoundingClientRect().width;
const displayWidth = e.getBoundingClientRect().width;
return {
offsetWidth: e.offsetWidth,
scrollWidth: e.scrollWidth,
fullWidth,
displayWidth,
truncated: fullWidth > displayWidth
};
} finally {
temp.remove();
}
}
function showSize(element, props) {
const offset = element.nextElementSibling;
const scroll = offset.nextElementSibling;
const display = scroll.nextElementSibling;
const full = display.nextElementSibling;
const truncated = full.nextElementSibling;

offset.textContent = props.offsetWidth;
scroll.textContent = props.scrollWidth;
display.textContent = props.displayWidth;

const fixed = props.fullWidth.toFixed(3);
full.innerHTML = fixed.replace(
/.?0+$/,
"<span class='invisible'>$&</span>"
);
truncated.textContent = props.truncated ? "✔" : undefined;
}
function showAllSizes() {
const query = ".container > .row:nth-child(n + 2) > *:first-child";
for (const element of document.querySelectorAll(query)) {
showSize(element, isEllipsisActive(element));
}
}
document.addEventListener("readystatechange", () => {
if (document.readyState !== "complete") {
return;
}
const width = document.getElementById("width");
width.addEventListener("change", () => {
document.querySelector(".container").style.gridTemplateColumns =
`${width.value}px repeat(5, auto)`;
showAllSizes();
});
showAllSizes();
});
* {
font-family: 'Roboto', sans-serif;
font-size: 14px;
}
.container {
display: inline-grid;
grid-template-columns: 295px repeat(5, auto);
gap: 8px;
padding: 8px;
border: 1px solid gray;
}
.container > .row {
display: contents;
}
.container > .row > * {
display: block;
border-width: 1px;
border-style: solid;
}
.container > .row:first-child > * {
font-weight: bold;
padding: 3px;
text-align: center;
border-color: gray;
background-color: silver;
}
.container > .row:nth-child(n + 2) > *:first-child {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border: 1px solid steelblue;
background-color: lightsteelblue;
}
.container
> .row:nth-child(n + 2)
> *:nth-child(n + 2):not(:last-child) {
border-color: khaki;
background-color: lemonchiffon;
text-align: right;
}
.container
> .row:nth-child(n + 2)
> *:last-child {
text-align: center;
}
.container
> .row:nth-child(n + 2)
> *:last-child:not(:empty) {
border-color: darkgreen;
background-color: green;
color: white;
}
.container
> .row:nth-child(n + 2)
> *:last-child:empty {
border-color: firebrick;
background-color: crimson;
}
.invisible {
visibility: hidden;
}
.test {
margin-top: 8px;
}
input[type="number"] {
margin-top: 4px;
text-align: right;
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
opacity: 1;
}
<head>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
</head>
<div class="container">
<div class="row">
<span>Text</span>
<span>Offset</span>
<span>Scroll</span>
<span>Display</span>
<span>Full</span>
<span>Truncated</span>
</div>

<div class="row">
<span>
<a>Analytics reports comes through garbled. Plsss</a>
</span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
<div class="row">
<span>
<a>Analytics reports comes through garbled. Plsssssss</a>
</span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
<div class="row">
<span>
<a>Normal text</a>
</span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
</div>
<div class="test">
<strong>
Try changing the width up or down a few pixels.<br />
</strong>
<label>
Width:
<input type="number" id="width" value="295" min="10" max="400" size="4" />
</label>
</div>

相关内容

  • 没有找到相关文章

最新更新