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.



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.


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

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

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.


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!


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

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


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


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

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

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>


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>


Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.