'HTML text-overflow ellipsis detection
I have a collection of block elements on a page. They all have the CSS rules white-space, overflow, text-overflow set so that overflowing text is trimmed and an ellipsis is used.
However, not all the elements overflow.
Is there anyway I can use javascript to detect which elements are overflowing?
Thanks.
Added: example HTML structure I am working with.
<td><span>Normal text</span></td>
<td><span>Long text that will be trimmed text</span></td>
The SPAN elements always fit in the cells, they have the ellipsis rule applied. I want to detect when the ellipsis is applied to the text content of the SPAN.
Solution 1:[1]
Once upon a time I needed to do this, and the only cross-browser reliable solution I came across was hack job. I'm not the biggest fan of solutions like this, but it certainly produces the correct result time and time again.
The idea is that you clone the element, remove any bounding width, and test if the cloned element is wider than the original. If so, you know it's going to have been truncated.
For example, using jQuery:
var $element = $('#element-to-test');
var $c = $element
.clone()
.css({display: 'inline', width: 'auto', visibility: 'hidden'})
.appendTo('body');
if( $c.width() > $element.width() ) {
// text was truncated.
// do what you need to do
}
$c.remove();
I made a jsFiddle to demonstrate this, http://jsfiddle.net/cgzW8/2/
You could even create your own custom pseudo-selector for jQuery:
$.expr[':'].truncated = function(obj) {
var $this = $(obj);
var $c = $this
.clone()
.css({display: 'inline', width: 'auto', visibility: 'hidden'})
.appendTo('body');
var c_width = $c.width();
$c.remove();
if ( c_width > $this.width() )
return true;
else
return false;
};
Then use it to find elements
$truncated_elements = $('.my-selector:truncated');
Demo: http://jsfiddle.net/cgzW8/293/
Hopefully this helps, hacky as it is.
Solution 2:[2]
Try this JS function, passing the span element as argument:
function isEllipsisActive(e) {
return (e.offsetWidth < e.scrollWidth);
}
Solution 3:[3]
Adding to italo's answer, you can also do this using jQuery.
function isEllipsisActive($jQueryObject) {
return ($jQueryObject.width() < $jQueryObject[0].scrollWidth);
}
Also, as Smoky pointed out, you may want to use jQuery outerWidth() instead of width().
function isEllipsisActive($jQueryObject) {
return ($jQueryObject.outerWidth() < $jQueryObject[0].scrollWidth);
}
Solution 4:[4]
For those using (or planning to use) the accepted answer from Christian Varga, please be aware of the performance issues.
Cloning/manipulating the DOM in such a way causes DOM Reflow (see an explanation on DOM reflow here) which is extremely resource intensive.
Using Christian Varga's solution on 100+ elements on a page caused a 4 second reflow delay during which the JS thread is locked. Considering JS is single-threaded this means a significant UX delay to the end user.
Italo Borssatto's answer should be the accepted one, it was approximately 10 times quicker during my profiling.
Solution 5:[5]
Answer from italo is very good! However let me refine it a little:
function isEllipsisActive(e) {
var tolerance = 2; // In px. Depends on the font you are using
return e.offsetWidth + tolerance < e.scrollWidth;
}
Cross browser compatibility
If, in fact, you try the above code and use console.log to print out the values of e.offsetWidth and e.scrollWidth, you will notice, on IE, that, even when you have no text truncation, a value difference of 1px or 2px is experienced.
So, depending on the font size you use, allow a certain tolerance!
Solution 6:[6]
elem.offsetWdith VS ele.scrollWidth This work for me! https://jsfiddle.net/gustavojuan/210to9p1/
$(function() {
$('.endtext').each(function(index, elem) {
debugger;
if(elem.offsetWidth !== elem.scrollWidth){
$(this).css({color: '#FF0000'})
}
});
});
Solution 7:[7]
This sample show tooltip on cell table with text truncated. Is dynamic based on table width:
$.expr[':'].truncated = function (obj) {
var element = $(obj);
return (element[0].scrollHeight > (element.innerHeight() + 1)) || (element[0].scrollWidth > (element.innerWidth() + 1));
};
$(document).ready(function () {
$("td").mouseenter(function () {
var cella = $(this);
var isTruncated = cella.filter(":truncated").length > 0;
if (isTruncated)
cella.attr("title", cella.text());
else
cella.attr("title", null);
});
});
Demo: https://jsfiddle.net/t4qs3tqs/
It works on all version of jQuery
Solution 8:[8]
All the solutions did not really work for me, what did work was compare the elements scrollWidth to the scrollWidth of its parent (or child, depending on which element has the trigger).
When the child's scrollWidth is higher than its parents, it means .text-ellipsis is active.
When el is the parent element
function isEllipsisActive(el) {
let width = el.offsetWidth;
let widthChild = el.firstChild.offsetWidth;
return (widthChild >= width);
}
When el is the child element
function isEllipsisActive(event) {
let width = el.offsetWidth;
let widthParent = el.parentElement.scrollWidth;
return (width >= widthParent);
}
Solution 9:[9]
My implementation)
const items = Array.from(document.querySelectorAll('.item'));
items.forEach(item =>{
item.style.color = checkEllipsis(item) ? 'red': 'black'
})
function checkEllipsis(el){
const styles = getComputedStyle(el);
const widthEl = parseFloat(styles.width);
const ctx = document.createElement('canvas').getContext('2d');
ctx.font = `${styles.fontSize} ${styles.fontFamily}`;
const text = ctx.measureText(el.innerText);
return text.width > widthEl;
}
.item{
width: 60px;
overflow: hidden;
text-overflow: ellipsis;
}
<div class="item">Short</div>
<div class="item">Loooooooooooong</div>
Solution 10:[10]
I think the better way to detect it is use getClientRects(), it seems each rect has the same height, so we can caculate lines number with the number of different top value.
getClientRects work like this
function getRowRects(element) {
var rects = [],
clientRects = element.getClientRects(),
len = clientRects.length,
clientRect, top, rectsLen, rect, i;
for(i=0; i<len; i++) {
has = false;
rectsLen = rects.length;
clientRect = clientRects[i];
top = clientRect.top;
while(rectsLen--) {
rect = rects[rectsLen];
if (rect.top == top) {
has = true;
break;
}
}
if(has) {
rect.right = rect.right > clientRect.right ? rect.right : clientRect.right;
rect.width = rect.right - rect.left;
}
else {
rects.push({
top: clientRect.top,
right: clientRect.right,
bottom: clientRect.bottom,
left: clientRect.left,
width: clientRect.width,
height: clientRect.height
});
}
}
return rects;
}
getRowRects work like this
you can detect like this
Solution 11:[11]
None of the solutions worked for me, so I chose a totally different approach. Instead of using the CSS solution with ellipsis, I just cut the text from a specific string length.
if (!this.isFullTextShown && this.text.length > 350) {
return this.text.substring(0, 350) + '...'
}
return this.text
and show "more/less" buttons if the length is exceeded.
<span
v-if="text.length > 350"
@click="isFullTextShown = !isFullTextShown"
>
{{ isFullTextShown ? 'show less' : 'show more' }}
</span>
Solution 12:[12]
The e.offsetWidth < e.scrollWidth solution is not always working.
And if you want to use pure JavaScript, I recommend to use this:
(typescript)
public isEllipsisActive(element: HTMLElement): boolean {
element.style.overflow = 'initial';
const noEllipsisWidth = element.offsetWidth;
element.style.overflow = 'hidden';
const ellipsisWidth = element.offsetWidth;
if (ellipsisWidth < noEllipsisWidth) {
return true;
} else {
return false;
}
}
Solution 13:[13]
The solution @ItaloBorssatto is perfect. But before looking at SO - I made my decision. Here it is :)
const elems = document.querySelectorAll('span');
elems.forEach(elem => {
checkEllipsis(elem);
});
function checkEllipsis(elem){
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const styles = getComputedStyle(elem);
ctx.font = `${styles.fontWeight} ${styles.fontSize} ${styles.fontFamily}`;
const widthTxt = ctx.measureText(elem.innerText).width;
if (widthTxt > parseFloat(styles.width)){
elem.style.color = 'red'
}
}
span.cat {
display: block;
border: 1px solid black;
white-space: nowrap;
width: 100px;
overflow: hidden;
text-overflow: ellipsis;
}
<span class="cat">Small Cat</span>
<span class="cat">Looooooooooooooong Cat</span>
Solution 14:[14]
there are some mistasks in demo http://jsfiddle.net/brandonzylstra/hjk9mvcy/ mentioned by https://stackoverflow.com/users/241142/iconoclast.
in his demo, add these code will works:
setTimeout(() => {
console.log(EntryElm[0].offsetWidth)
}, 0)
Solution 15:[15]
If you're doing react, here's how I did it.
<div
ref={ref => {
if (!ref) return
const isOverflowing = ref.scrollWidth > ref.clientWidth
if (isOverflowing) {
// handle what to do next here
}
}}
/>
Solution 16:[16]
Adding to @??????? answer, missing deduction of borders and paddings to be fully functional!!
const items = Array.from(document.querySelectorAll('.item'));
items.forEach(item =>{
item.style.color = checkEllipsis(item) ? 'red': 'black'
})
function checkEllipsis(el){
const styles = getComputedStyle(el);
const widthEl = parseFloat(styles.width);
const ctx = document.createElement('canvas').getContext('2d');
ctx.font = `${styles.fontSize} ${styles.fontFamily}`;
const text = ctx.measureText(el.innerText);
let extra = 0;
extra += parseFloat(styles.getPropertyValue('border-left-width'));
extra += parseFloat(styles.getPropertyValue('border-right-width'));
extra += parseFloat(styles.getPropertyValue('padding-left'));
extra += parseFloat(styles.getPropertyValue('padding-right'));
return text.width > (widthEl - extra);
}
.item{
width: 60px;
overflow: hidden;
text-overflow: ellipsis;
}
<div class="item">Short</div>
<div class="item">Loooooooooooong</div>
Solution 17:[17]
For someone who uses e.offsetWidth < e.scrollWidth and got a bug that can show full text but still got ellipsis.
It because offsetWidth and scrollWidth always round the value. For example: offsetWidth return 161 but the actual width is 161.25.
The solution is use getBoundingClientRect
const clonedEl = e.cloneNode(true)
clonedElement.style.overflow = "visible"
clonedElement.style.visibility = "hidden"
clonedElement.style.width = "fit-content"
e.parentElement.appendChild(clonedEl)
const fullWidth = clonedElement.getBoundingClientRect().width
const currentWidth = e.getBoundingClientRect().width
return currentWidth < fullWidth
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
