Detecting text overflow with ellipsis in HTML

I am facing an issue with certain elements on my webpage that have the CSS properties white-space, overflow, and text-overflow set in a way that causes text overflow to be trimmed with an ellipsis.

div {
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;

  border: 1px solid black;
  width: 150px;
  margin: 5px;
  padding: 5px;
  font-family: sans-serif;
}
<div>
  <span>Normal text</span>
</div>
<div>
  <span>Long text that will be trimmed text</span>
</div>

Is there a JavaScript solution available to identify which elements have overflowing content?

Answer №1

Here's a handy JavaScript function that you can use with a span element as an argument:

function checkEllipsis(e) {
     return (e.offsetWidth < e.scrollWidth);
}

Answer №2

Once upon a time, I found myself in need of a solution for detecting truncated text that would work reliably across different browsers. The method I stumbled upon may be considered a bit of a hack, but it consistently delivers the correct result.

The concept involves cloning the element, removing any set width constraints, and comparing the width of the cloned element to the original. If the cloned element is wider than the original, then you can infer that the text has been truncated.

Here's an 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. 
    // perform necessary actions
}

$c.remove();

I've created a jsFiddle to showcase this technique: http://jsfiddle.net/cgzW8/2/

If preferred, you could even define a 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;
};

This allows you to easily identify truncated elements with your custom pseudo-selector:

$truncated_elements = $('.my-selector:truncated');

Check out the demo here: http://jsfiddle.net/cgzW8/293/

While somewhat unconventional, I hope this approach proves helpful in handling truncated text effectively.

Answer №3

Expanding on italo's response, you have the option to achieve this functionality with jQuery.

function checkEllipsis($element) {
    return ($element.width() < $element[0].scrollWidth);
}

In addition, as noted by Smoky, consider utilizing jQuery's outerWidth() instead of width().

function checkEllipsis($element) {
    return ($element.outerWidth() < $element[0].scrollWidth);
}

Answer №4

Great answer from italo with some valuable insights! However, I would like to add a little refinement:

function checkEllipsisActive(element) {
   var margin = 3; // Adjust based on font and browser differences
   return element.offsetWidth + margin < element.scrollWidth;
}

Ensuring Compatibility Across Different Browsers

If you test the code above and use console.log to view the values of element.offsetWidth and element.scrollWidth, you may observe slight variations in Internet Explorer even when there is no text truncation, typically off by 1 or 2 pixels.

To accommodate these nuances in font rendering and browser behavior, it's advisable to allow for a certain tolerance!

Answer №5

Code Implementation

const elements = Array.from(document.querySelectorAll('.element'));
elements.forEach(element => {
    element.style.color = checkEllipsis(element) ? '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;
}
.element{
  width: 60px;
  overflow: hidden;
  text-overflow: ellipsis;
}
      <div class="element">Short</div>
      <div class="element">Loooooooooooong</div>

Answer №6

This code snippet demonstrates how to display a tooltip on a cell within a table with truncated text, adjusting dynamically based on the width of the table:

$.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);
    });
});

Check out the demo here: https://jsfiddle.net/t4qs3tqs/

This functionality is compatible with all versions of jQuery.

Answer №7

None of the solutions I tried seemed to work for my specific situation. What ended up being successful was comparing the scrollWidth of an element to that of its parent (or child, depending on which one is triggering the behavior).

If the child element's scrollWidth surpasses that of its parent, it indicates that .text-ellipsis is active.


When el serves as the parent element

function isEllipsisActive(el) {
    let width       = el.offsetWidth;
    let widthChild  = el.firstChild.offsetWidth;
    return (widthChild >= width);
}

When el is acting as the child element

function isEllipsisActive(event) {
    let width       = el.offsetWidth;
    let widthParent = el.parentElement.scrollWidth;
    return (width >= widthParent);
}

Answer №8

Comparing elem.offsetWdith to ele.scrollWidth I found a solution that works perfectly for me! Check out my code snippet on this JSFiddle link

$(function() {
  $('.endtext').each(function(index, elem) {
    debugger;
    if(elem.offsetWidth !== elem.scrollWidth){
      $(this).css({color: '#FF0000'})
    }
  });
});

Answer №9

Expanding on @Дмытрык's response, it is important to remember to take into account borders and paddings for the code to work effectively!

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>

Answer №10

There seems to be a slight pixel discrepancy with the previously mentioned solutions when comparing offsetWidth > scrollWidth.

The W3C has an older API that rounds off the element.scrollWidth value, leading to incorrect comparisons in certain cases. For example, if the element's width is 150px and the scrollWidth is 150.4px (rounded to 150), the comparison will erroneously return false, even if the browser displays ellipsis in the text.

Efforts were made to update APIs that return fractional pixels, but these changes were rolled back due to compatibility issues with existing web content.

A workaround involves using max-content and getClientRects(). Here's a sample code snippet that I have used onMouseEnter. Please note that this workaround only functions correctly if the container spans 100% of the available width (for instance, if you are utilizing flexbox, ensure your container is set to flex: 1).

hasEllipsis(elementItem) {
    let scrollWidth = elementItem.scrollWidth;
    
    elementItem.style.width = 'max-content';
    const itemRects = elementItem.getClientRects();

    if (itemRects.length > 0 && itemRects[0].width > scrollWidth) {
        scrollWidth = itemRects[0].width;
    }

    elementItem.style.width = 'auto';
    return scrollWidth > elementItem.clientWidth;
}

Related Articles:

https://bugs.chromium.org/p/chromium/issues/detail?id=980476

https://github.com/w3c/csswg-drafts/issues/4123

Answer №11

When the scrollHeight is greater than the clientHeight, it indicates that there is a truncation occurring:

https://i.sstatic.net/jqcYL.png

Resizable Example:

const detectTextTruncation = node => node.scrollHeight > node.clientHeight
const applyTruncationClass = node => node.classList.toggle('truncated', detectTextTruncation(node))

// Resize observer for DOM elements
const resizeObserver = new ResizeObserver(entries => applyTruncationClass(entries[0].target))
resizeObserver.observe(document.querySelector('p'), { attributes: true })
p {
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  
  width: 300px;
  resize: horizontal;
  overflow: hidden;
}

/* Custom style for detecting truncation 
 ***************************************/
body:has(.truncated)::after {
  content: 'Truncated';
  font: 24px Arial;
  background: red;
  color: white;
  padding: 8px;
  position: absolute;
  bottom: 1em;
  right: 1em;
}
<p>
  <b>Resize this text block:</b> Lorem ipsum dolor sit amet, consectetur adipiscing elit, 
  sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 
  Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris 
  nisi ut aliquip ex ea commodo consequat.
</p>

👉 Check out my customizable example on CodePen

Answer №12

In my opinion, a more effective way to detect it would be to utilize the getClientRects() method; each rectangle seems to have the same height, allowing us to determine the number of lines by counting the different top values.

The functionality of getClientRects is demonstrated here

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;
}

The behavior of getRowRects can be seen in action here

You can implement detection similar to what is shown here

Answer №13

In case you are working with react, this is the approach I took.

<div 
  ref={ref => {
    if (!ref) return
    const isExceeding = ref.scrollWidth > ref.clientWidth
    if (isExceeding) {
      // add your logic here
    }
  }}
/>

Answer №14

When utilizing the code e.offsetWidth < e.scrollWidth, an issue may arise where the full text is displayed but ellipsis are still present.

This issue occurs because both offsetWidth and scrollWidth round their values, resulting in discrepancies. For instance, offsetWidth may return 161 while the actual width is 161.25. The solution lies in using getBoundingClientRect

const clonedEl = e.cloneNode(true)
clonedElement.style.overflow = "visible"
cllonedElement.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

Answer №15

I didn't have success with the suggested solutions, so I took a different approach. Instead of utilizing CSS for ellipsis, I decided to trim the text at a specific length.

if (!this.isFullTextShown && this.text.length > 350) {
  return this.text.substring(0, 350) + '...'
}
return this.text

I also implemented "more/less" buttons when the text exceeds the specified length.

<span
  v-if="text.length > 350"
  @click="isFullTextShown = !isFullTextShown"
>
  {{ isFullTextShown ? 'show less' : 'show more' }}
</span>

Answer №16

If you are implementing the line-clamp property for adding ellipsis to more than one line, you can incorporate this check:

if (
      contentElement &&
      contentElement.offsetHeight < contentElement.scrollHeight
    ) {
      // apply text-overflow
    }

Answer №17

After testing various methods, I found that the common approaches like

offsetWidth, clientWidth, scrollWidth
were not accurate enough to detect if the ellipsis was active. Here is the solution I came up with:

const hasEllipsis = (element: HTMLElement) => {
  // determine if text overflows
  // use range-based check for more precise content width measurement
  const range = document.createRange();
  range.selectNodeContents(element);
  const rangeWidth = range.getBoundingClientRect().width;
  // use getBoundingClientRect for better precision
  const elementWidth = element.getBoundingClientRect().width;

  const isOverflowing = rangeWidth > elementWidth;

  return isOverflowing;
};

Answer №18

One should be cautious when relying on the e.offsetWidth < e.scrollWidth solution as it may not always work as expected.

For those looking to utilize pure JavaScript, I suggest using the following method:

(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;
    }
}

Answer №19

Before seeking help on SO, I came up with my own solution, which turned out to be similar to @ItaloBorssatto's perfect answer.

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>

Answer №20

There are a few errors that were pointed out in the demonstration provided at http://jsfiddle.net/brandonzylstra/hjk9mvcy/ by .

To make it work properly, you can include the following code:

setTimeout(() => {      
  console.log(EntryElm[0].offsetWidth)
}, 0)

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Online application for saving a vast quantity of information on the user's device

Is there a way for a web application to store an extensive amount of data client-side, allowing for millions of records to be accessed offline by users exclusively on Chrome? I initially considered indexedDb, but I discovered it becomes almost unusable wi ...

Invoke the forEach method within a lambda function and assign the output to a variable

In order to achieve my goal, I am looking for a way to take an array as input and save the result for future use. Unfortunately, I do not have permission to create additional functions. As a result, the code must accurately reflect what result should be. l ...

The form will not automatically fill in the date

I am currently trying to format a date retrieved from my PSQL database, which looks like 2023-04-05T05:00:00.000Z, into the mm/dd/yyyy format for display in a date input field. I attempted to achieve this using the following code: const dateOfOrder = d ...

Achieving a floating effect by moving elements from the bottom left corner to the top right corner

Is there an innovative and efficient method to align elements from the bottom left corner to the top right corner? I have attempted using display: grid, but it is too rigid with fixed columns and a limit of 10 items. I am in search of a clever solution th ...

The art of organizing information: Tables and data management

Looking for a solution with a table: <table> <tr> <td>1</td> <td>2</td> <td>3</td> <td>4</td> </tr> </table> Trying to format the cells to display as: 1 2 3 4 ...

The date error from day.js in Firefox is not valid

My date is formatted as 2022-01-27 09:23:48 UTC and I am trying to parse it into MMMM-DD-YYYY format (Jan-27-2022) using day.js. The parsing works well in Chrome, but Firefox returns an 'Invalid' result. import dayjs from "dayjs" const ...

Submit HTML and JavaScript information to a PHP document

I have a form with a single file input and a submit button. The action attribute of the form is set to 'upload.php' where my PHP script resides for renaming and saving the uploaded file to a specific folder. Additionally, I require some JavaScrip ...

What could be causing this axios.get request to fail?

I am currently enrolled in Colt Steele's Web Developer bootcamp and facing a challenge... In the tutorial, we are using axios as 'request' has been discontinued, so I am trying to follow along with this change. My goal is to set up a Get r ...

How can I identify when a CSS transition on a particular element has ended if there are several transitions occurring simultaneously?

In my coding, I have been utilizing the following method to identify when a CSS3 transition has finished: CACHE.previewControlWrap.css({ 'bottom':'-217px' }).one('webkitTransitionEnd transitionend m ...

Evening rotation featuring a CSS design reminiscent of f.lux

Would it be feasible to develop a feature akin to f.lux for my website? f.lux emulates a night shift - I am interested in implementing a similar function on my Web-App. For instance, a CSS property that adjusts the colors to a specific temperature. Curr ...

Vue: Component designed exclusively for select pages

In my Symfony project, I am utilizing Vue.js (2) and I currently have a Vue instance with "global" components (specifically an Editor component with some CKEditor libraries) precompiled using the default symfony webpack setup. //app.js import Vue from &apo ...

Guide on incorporating a glyphicon into a dropdown menu link by leveraging the link_to helper in Ruby on Rails

Just getting started here and I have a question... I want to include glyphicons on the links in a dropdown menu using the link_to helper in my Ruby on Rails app. This is the code I currently have: <ul class="nav_item pull-right"> <li cla ...

Using an external script to modify or call a Vue.js method

My Vue app is constructed using Webpack and includes a few basic computed properties, such as calculating the sum amount from input values. However, I now require the capability to replace the summation function with one stored in a separate file that is n ...

Calculate the mean value of each array, and then determine which average is higher

After running the code snippet provided, I noticed that it outputs "first" instead of "second". Can you help me verify my logic here? I first calculate both averages and then compare them to return the greater one. Do you see any issues with this approac ...

"Enhance Your Online Shopping Experience with Woocommerce Ajax Filters and

I have a structure that looks like this: Company Google Apple Microsoft Material Wood Metal Plastic Essentially, Brand & Material are attributes in the Woocommerce system, while the rest are variables. I am currently working on implementing AJAX fi ...

The necessary parameters are not provided for [Route: bill] [URI: bill/{id}]

I'm having trouble locating the error in my code. Here is my controller: public function editBill(Request $req) { $customerInfo=Customer::where('id',$req->id)->first(); $billInfo=Bill::where('id',$req->id)->get( ...

The control buttons for the HTML video element are hidden

When I create a video element and set controls to true, the controls are not showing even on hover. if (!this._player) { this._player = document.createElement('video'); } this._player.controls = true; this._player.src = xxxxx; I have also s ...

Ways to conceal a div in React when the input or child is not in focus

My custom search and select component includes an input field and a results list enclosed in a wrapper container: <div className='wrapper'> <input /> <div className='results'> <div>Result Item</div> ...

Step-by-step guide on showcasing a quiz utilizing Magnific-popup and ajax

My goal is to create a functionality that allows users to easily download and view a quiz in a lightbox with just one click. I have successfully set up the ajax features and believe I am using Magnific-popup correctly, but for some reason, the lightbox is ...

How can the vertical scroll bar position be reset in Material-Table when pagination is activated?

Whenever I scroll up or down in my Material-Table, and then switch pages, the vertical scroll bar doesn't reset to the top of the table. Instead, it stays where it was before changing the page, causing confusion for users. The only mention of scroll ...