Detección de puntos suspensivos de desbordamiento de texto HTML

176

Tengo una colección de elementos de bloque en una página. Todos tienen las reglas CSS espacio en blanco, desbordamiento, desbordamiento de texto establecido para que el texto desbordado se recorte y se use una elipsis.

Sin embargo, no todos los elementos se desbordan.

¿Hay alguna forma de usar JavaScript para detectar qué elementos se desbordan?

Gracias.

Agregado: ejemplo de estructura HTML con la que estoy trabajando.

<td><span>Normal text</span></td>
<td><span>Long text that will be trimmed text</span></td>

Los elementos SPAN siempre caben en las celdas, tienen aplicada la regla de puntos suspensivos. Quiero detectar cuándo se aplican los puntos suspensivos al contenido de texto de SPAN.

deanoj
fuente
10
No es un duplicado! Esa pregunta es sobre un elemento dentro de otro, elemento padre. Estoy hablando de texto dentro de un solo elemento. En mi caso, el SPAN en el TD nunca desborda el TD, es el texto dentro del SPAN que se desborda y se recorta. ¡Eso es lo que estoy tratando de detectar! Lo siento, podría haber planteado esta pregunta mejor, lo admito.
deanoj
Oh, olvidé agregar: esto solo necesita trabajar en webkit si eso ayuda ...
deanoj
Hice Ghommey solo para ver si funcionaba ... no funcionó.
deanoj
el aspecto de puntos suspensivos es irrelevante; todo lo que necesita detectar es si está desbordado.
Spudley

Respuestas:

114

Érase una vez que necesitaba hacer esto, y la única solución confiable entre navegadores que encontré fue el trabajo de pirateo. No soy el mayor fanático de soluciones como esta, pero ciertamente produce el resultado correcto una y otra vez.

La idea es que clone el elemento, elimine cualquier ancho delimitador y pruebe si el elemento clonado es más ancho que el original. Si es así, sabes que se habrá truncado.

Por ejemplo, usando 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();

Hice un jsFiddle para demostrar esto, http://jsfiddle.net/cgzW8/2/

Incluso podría crear su propio pseudo-selector personalizado para 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;
};

Luego úsalo para encontrar elementos

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

Demostración: http://jsfiddle.net/cgzW8/293/

Esperemos que esto ayude, hacky como es.

cristiano
fuente
1
@Lauri No; El truncamiento CSS no cambia el texto real en el cuadro, por lo que el contenido es siempre el mismo, ya sea truncado, visible u oculto. Todavía no hay forma de obtener programáticamente el texto truncado, si lo hubiera, ¡no necesitaría clonar el elemento en primer lugar!
Christian
1
Parece que esto no funcionará en situaciones donde no hay white-space: nowrap.
Jakub Hampl
2
Debo decir que después de buscar MUCHO en Internet e intenté implementar muchas soluciones, esta fue, con mucho, la más confiable que encontré. Esta solución no da resultados diferentes entre navegadores como element.innerWidth o element.OffsetWidth lo que tiene problemas al usar márgenes o relleno. Gran solución, bien hecho.
Inscripción
1
Para mí, esto no funcionó en ningún lado. No estoy seguro de cómo depende de CSS (he intentado usarlo en los controles de entrada, las soluciones a continuación funcionaron al menos en Chrome y Firefox (pero no en IE11)) ...
Alexander
3
Gran solución, especialmente usando el pseudo-selector jQuery. Pero a veces puede no funcionar, porque el ancho se calcula incorrectamente si el texto del elemento tiene un estilo diferente (tamaño de fuente, espacio entre letras, márgenes de elementos internos). En ese caso, recomendaría agregar el elemento clone a $ this.parent () en lugar de 'body'. Esto le dará una copia exacta del elemento y los cálculos serán correctos. Gracias de todos modos
Alex
286

Pruebe esta función JS, pasando el elemento span como argumento:

function isEllipsisActive(e) {
     return (e.offsetWidth < e.scrollWidth);
}
Italo Borssatto
fuente
10
Esta debería ser la respuesta: muy elegante y funciona en Chrome e IE (9)
Roy Truelove
14
Esta respuesta y la respuesta de Alex no funcionarán en IE8; Hay algunos casos extraños en los que el ancho de desplazamiento y el ancho exterior son iguales ... pero tiene puntos suspensivos, y luego algunos casos en los que son iguales ... y NO hay puntos suspensivos. En otras palabras, la primera solución es la única que funciona en todos los navegadores.
3
Para aquellos que necesitan comprender offsetWidth, scrollWidth y clientWidth, aquí hay una muy buena explicación: stackoverflow.com/a/21064102/1815779
Linh Dam
44
En text-overflow:ellipsisdebería sere.offsetWidth <= e.scrollWidth
oriadam
44
Siempre son iguales entre sí en niños flexibles y, por lo tanto, no funcionarán para ellos.
Kevin Beal
14

Agregando a la respuesta de italo, también puedes hacer esto usando jQuery.

function isEllipsisActive($jQueryObject) {
    return ($jQueryObject.width() < $jQueryObject[0].scrollWidth);
}

Además, como señaló Smoky, es posible que desee utilizar jQuery outsideWidth () en lugar de width ().

function isEllipsisActive($jQueryObject) {
    return ($jQueryObject.outerWidth() < $jQueryObject[0].scrollWidth);
}
Alex K
fuente
9

Para aquellos que usan (o planean usar) la respuesta aceptada de Christian Varga, tengan en cuenta los problemas de rendimiento.

Clonar / manipular el DOM de tal manera causa el Reflujo del DOM (vea una explicación sobre el reflujo del DOM) aquí), que recursos.

El uso de la solución de Christian Varga en más de 100 elementos en una página provocó un retraso de reflujo de 4 segundos durante el cual el hilo JS está bloqueado. Teniendo en cuenta que JS tiene un solo subproceso, esto significa un retraso significativo de UX para el usuario final.

La respuesta de Italo Borssatto debería ser la aceptada, fue aproximadamente 10 veces más rápida durante mi perfil.

RyanGled
fuente
2
Lamentablemente no funciona en todos los escenarios (ojalá lo hiciera). Y el impacto de rendimiento que menciona aquí se puede compensar solo ejecutándolo para la situación en la que necesita verificar, como solo mouseenterpara ver si es necesario mostrar una información sobre herramientas.
Jacob
5

¡La respuesta de italo es muy buena! Sin embargo, permítanme refinarlo un poco:

function isEllipsisActive(e) {
   var tolerance = 2; // In px. Depends on the font you are using
   return e.offsetWidth + tolerance < e.scrollWidth;
}

Compatibilidad cruzada del navegador

Si, de hecho, prueba el código anterior y lo usa console.logpara imprimir los valores de , e.offsetWidthy e.scrollWidthnotará, en IE, que, incluso cuando no tiene truncamiento de texto, hay una diferencia de valor de 1pxo2px se experimenta .

Entonces, dependiendo del tamaño de fuente que uses, ¡permite una cierta tolerancia!

Andry
fuente
No funciona en IE10: offsetWidth es idéntico a scrollWidth, ambos me dan el ancho truncado.
SsjCosty
1
También necesito esta tolerancia en Chrome 60 (más reciente).
Jan Żankowski
5

elem.offsetWdith VS ele.scrollWidth ¡Esto funciona para mí! https://jsfiddle.net/gustavojuan/210to9p1/

$(function() {
  $('.endtext').each(function(index, elem) {
    debugger;
    if(elem.offsetWidth !== elem.scrollWidth){
      $(this).css({color: '#FF0000'})
    }
  });
});
Gustavo Juan
fuente
Ni funciona en Chrome ni Firefox actuales. Ambos elementos se vuelven rojos.
xehpuk
4

Este ejemplo muestra información sobre herramientas en la tabla de celdas con texto truncado. Es dinámico según el ancho de la tabla:

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

Manifestación: https://jsfiddle.net/t4qs3tqs/

Funciona en todas las versiones de jQuery

rojo
fuente
1

Creo que la mejor manera de detectarlo es usarlo getClientRects(), parece que cada rect tiene la misma altura, por lo que podemos calcular el número de líneas con el número de diferentestop valor .

getClientRectsobra como esta

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 trabaja como esta

se puede detectar como este

Defims
fuente
1

Todas las soluciones realmente no funcionaron para mí, lo que funcionó fue comparar los elementos scrollWidthconscrollWidth de su padre (o hijo, dependiendo de qué elemento tenga el disparador).

Cuando el niño scrollWidthes más alto que sus padres, significa que .text-ellipsisestá activo.


Cuando event es el elemento padre

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

Cuando eventes el elemento hijo

function isEllipsisActive(event) {
    let el          = event.currentTarget;
    let width       = el.offsetWidth;
    let widthParent = el.parentElement.scrollWidth;
    return (width >= widthParent);
}
Jeffrey Roosendaal
fuente
0

los e.offsetWidth < e.scrollWidth solución no siempre funciona.

Y si quieres usar JavaScript puro, te recomiendo usar esto:

(mecanografiado)

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;
    }
}
Sarel Zioni
fuente
0

La solución @ItaloBorssatto es perfecta. Pero antes de mirar SO, tomé mi decisión. Aquí está :)

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>

Дмытрык
fuente
0

Mi implementación)

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>

Дмытрык
fuente