Detecção de reticências de transbordamento de texto HTML

176

Eu tenho uma coleção de elementos de bloco em uma página. Todos eles têm as regras de CSS espaço em branco, estouro, estouro de texto definidas para que o texto excedente seja cortado e reticências sejam usadas.

No entanto, nem todos os elementos estão excedidos.

Existe alguma maneira que eu possa usar o javascript para detectar quais elementos estão transbordando?

Obrigado.

Adicionado: exemplo de estrutura HTML com a qual estou trabalhando.

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

Os elementos SPAN sempre se encaixam nas células, eles têm a regra de reticências aplicada. Quero detectar quando as reticências são aplicadas ao conteúdo de texto do SPAN.

deanoj
fonte
10
Não é uma duplicata! Essa pergunta é sobre um elemento dentro de outro, elemento pai. Estou falando de texto em um único elemento. No meu caso, o SPAN no TD nunca excede o TD, é o texto dentro do SPAN que transborda e é cortado. É isso que estou tentando detectar! Desculpe - eu poderia ter feito essa pergunta melhor, admito.
deanoj
Ah, eu esqueci de acrescentar - isso só precisa trabalhar em webkit se isso ajuda ...
deanoj
Eu fiz Ghommey só para ver se funcionou ... não funcionou.
deanoj
o aspecto da elipse é irrelevante; tudo o que você precisa detectar é se está cheio.
Spudley

Respostas:

114

Era uma vez que eu precisava fazer isso, e a única solução confiável entre navegadores que encontrei foi o hack job. Não sou o maior fã de soluções como essa, mas certamente produz o resultado correto repetidamente.

A idéia é que você clone o elemento, remova qualquer largura delimitadora e teste se o elemento clonado é maior que o original. Nesse caso, você sabe que terá sido truncado.

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

Eu fiz um jsFiddle para demonstrar isso, http://jsfiddle.net/cgzW8/2/

Você pode até criar seu próprio pseudo-seletor 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;
};

Em seguida, use-o para encontrar elementos

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

Demonstração: http://jsfiddle.net/cgzW8/293/

Espero que isso ajude, hacky como é.

cristão
fonte
1
@Lauri No; O truncamento de CSS não altera o texto real na caixa; portanto, o conteúdo é sempre o mesmo, seja truncado, visível ou oculto. Ainda não há como obter programaticamente o texto truncado; se houver, você não precisará clonar o elemento em primeiro lugar!
Christian
1
Parece que isso não funcionará em situações em que não há white-space: nowrap.
Jakub Hampl
2
Devo dizer que, depois de pesquisar muito na internet e tentar implementar muitas soluções, essa foi de longe a mais confiável que eu encontrei. Esta solução não fornece resultados diferentes entre navegadores como element.innerWidth ou element.OffsetWidth apresenta problemas ao usar margens ou preenchimento. Ótima solução, Muito bem.
Scription
1
Para mim, isso não funcionou em nenhum lugar. Eu não tenho certeza, como ele depende de CSS (eu tentei usá-lo em controles de entrada, as soluções abaixo trabalhado pelo menos no Chrome e Firefox (mas não no IE11)) ...
Alexander
3
Ótima solução, especialmente usando o pseudo-seletor jQuery. Mas às vezes pode não funcionar, porque a largura é calculada incorretamente se o texto do elemento tiver um estilo diferente (tamanho da fonte, espaçamento entre letras, margens dos elementos internos). Nesse caso, eu recomendaria anexar o elemento clone a $ this.parent () em vez de 'body'. Isso fornecerá a cópia exata do elemento e os cálculos estarão corretos. Obrigado de qualquer forma
Alex
286

Experimente esta função JS, passando o elemento span como argumento:

function isEllipsisActive(e) {
     return (e.offsetWidth < e.scrollWidth);
}
Italo Borssatto
fonte
10
Esta deve ser a resposta - muito elegante e funciona em Chrome e IE (9)
Roy Truelove
14
Esta resposta e a resposta de Alex não funcionarão no IE8; há alguns casos estranhos em que a largura e a largura externa do rolo são iguais ... mas possui reticências e, em alguns casos, onde são iguais ... e NÃO há reticências. Em outras palavras, a primeira solução é a única que funciona em todos os navegadores.
3
Para aqueles que precisam entender offsetWidth, scrollWidth e clientWidth, aqui está uma explicação muito boa: stackoverflow.com/a/21064102/1815779
Linh Dam
4
Por text-overflow:ellipsisque deveria sere.offsetWidth <= e.scrollWidth
oriadam
4
Eles são sempre iguais entre os filhos flexíveis e, portanto, não funcionam para eles.
Kevin Beal
14

Adicionando à resposta do italo, você também pode fazer isso usando o jQuery.

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

Além disso, como Smoky apontou, convém usar o jQuery outerWidth () em vez da largura ().

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

Para aqueles que usam (ou planejam usar) a resposta aceita por Christian Varga, esteja ciente dos problemas de desempenho.

A clonagem / manipulação do DOM dessa maneira causa o DOM Reflow (veja uma explicação sobre o DOM reflow aqui), que consome extremamente recursos.

O uso da solução de Christian Varga em mais de 100 elementos em uma página causou um atraso de refluxo de 4 segundos durante o qual o encadeamento JS está bloqueado. Considerando que o JS é de thread único, isso significa um atraso significativo no UX para o usuário final.

A resposta de Italo Borssatto deve ser a aceita, foi aproximadamente 10 vezes mais rápida durante meu perfil.

RyanGled
fonte
2
Infelizmente, ele não funciona em todos os cenários (eu gostaria que fosse). E o desempenho atingido que você mencionou aqui pode ser compensado executando-o apenas para a situação em que você precisa verificar - como apenas mouseenterpara ver se uma dica de ferramenta precisa ser mostrada.
Jacob
5

A resposta do italo é muito boa! No entanto, deixe-me refinar um pouco:

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

Compatibilidade entre navegadores

Se, de fato, tentar o código e uso superior console.logpara imprimir os valores de e.offsetWidthe e.scrollWidth, você vai notar, no IE, que, mesmo quando você não tem truncagem de texto, uma diferença valor 1pxou 2pxé experiente.

Portanto, dependendo do tamanho da fonte usado, permita uma certa tolerância!

Andry
fonte
Ele não funciona no IE10 - offsetWidth é idêntico ao scrollWidth, ambos fornecendo a largura truncada.
precisa saber é o seguinte
1
Também preciso dessa tolerância no Chrome 60 (mais recente).
Jan Żankowski
5

elem.offsetWdith VS ele.scrollWidth Este trabalho para mim! https://jsfiddle.net/gustavojuan/210to9p1/

$(function() {
  $('.endtext').each(function(index, elem) {
    debugger;
    if(elem.offsetWidth !== elem.scrollWidth){
      $(this).css({color: '#FF0000'})
    }
  });
});
Gustavo Juan
fonte
Nem funciona no Chrome atual nem no Firefox. Ambos os elementos ficam vermelhos.
xehpuk
4

Este exemplo mostra a dica de ferramenta na tabela de células com o texto truncado. É dinâmico com base na largura da tabela:

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

Demonstração: https://jsfiddle.net/t4qs3tqs/

Funciona em todas as versões do jQuery

Vermelho
fonte
1

Acho que a melhor maneira de detectá-lo é usar getClientRects(), parece que cada retângulo tem a mesma altura, para que possamos calcular o número de linhas com o número de topvalor diferente .

getClientRectstrabalho como este

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

getRowRectstrabalho como este

você pode detectar como este

Defims
fonte
1

Todas as soluções realmente não funcionaram para mim, o que funcionou foi comparar os elementos scrollWidthcom os scrollWidthde seu pai (ou filho, dependendo de qual elemento possui o gatilho).

Quando a criança scrollWidthé mais alta que seus pais, significa que .text-ellipsisestá ativa.


Quando eventé o elemento pai

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

Quando eventé o elemento filho

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

A e.offsetWidth < e.scrollWidthsolução nem sempre está funcionando.

E se você quiser usar JavaScript puro, recomendo usar isso:

(datilografado)

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
fonte
0

A solução @ItaloBorssatto é perfeita. Mas antes de olhar para a SO - tomei minha decisão. Aqui 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>

Дмытрык
fonte
0

Minha implementação)

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>

Дмытрык
fonte