Como posso contar linhas de texto dentro de um elemento DOM? Eu posso?

127

Gostaria de saber se existe uma maneira de contar linhas dentro de uma div, por exemplo. Digamos que temos uma div assim:

<div id="content">hello how are you?</div>

Dependendo de muitos fatores, a div pode ter uma, duas ou até quatro linhas de texto. Existe alguma maneira de o script saber?

Em outras palavras, as interrupções automáticas são representadas no DOM?

buti-oxa
fonte

Respostas:

89

Se o tamanho da div depende do conteúdo (que eu assumo ser o caso da sua descrição), você pode recuperar a altura da div usando:

var divHeight = document.getElementById('content').offsetHeight;

E divida pela altura da linha da fonte:

document.getElementById('content').style.lineHeight;

Ou, para obter a altura da linha, se ela não tiver sido definida explicitamente:

var element = document.getElementById('content');
document.defaultView.getComputedStyle(element, null).getPropertyValue("lineHeight");

Você também precisará levar em consideração o preenchimento e o espaçamento entre linhas.

EDITAR

Teste totalmente independente, definindo explicitamente a altura da linha:

function countLines() {
   var el = document.getElementById('content');
   var divHeight = el.offsetHeight
   var lineHeight = parseInt(el.style.lineHeight);
   var lines = divHeight / lineHeight;
   alert("Lines: " + lines);
}
<body onload="countLines();">
  <div id="content" style="width: 80px; line-height: 20px">
    hello how are you? hello how are you? hello how are you? hello how are you?
  </div>
</body>

Piscina
fonte
7
É um bom começo, exceto que a altura da linha nem sempre pode ter sido definida. Você deve obter isso do estilo calculado do elemento.
Chetan S
Pensando bem, a altura da linha nem sempre precisa ser numérica (ou em pixels). Portanto, se seu código depende dele e sua convenção de CSS permite, você provavelmente deve definir uma altura de linha em pixels.
Chetan S
6
@Chetan - definir as dimensões do texto em pixels é geralmente considerado uma coisa ruim. astahost.com/Sizes-Webdesign-Em-Vs-Px-t8926.html
annakata:
6
Isso pode funcionar apenas no caso mais simples (como no meu exemplo). Se houver vãos internos, elementos de bloco embutido e assim por diante, a divisão direta pelo tamanho da fonte (pai) não terá valor. Ainda assim, é melhor que nada, obrigado.
buti-oxa
2
Acho que um melhor método de obter um computado line-height(que não é explicitamente definido) seria usarwindow.getComputedStyle(element, null).getPropertyValue('line-height')
rnevius
20

Uma solução é colocar todas as palavras em uma tag span usando script. Então, se a dimensão Y de uma determinada tag de span for menor que a de seu predecessor imediato, ocorrerá uma quebra de linha.

Bob Brunius
fonte
Inteligente! Como você faz isso? Eu acho que você assume o texto apenas parágrafo (como eu fiz no exemplo). Eu não tinha essa limitação em mente, mas pode estar tudo bem, desde que o script não falhe quando houver algo mais dentro do parágrafo.
buti-oxa
1
Pensando bem, esse método falha se houver vãos alinhados verticalmente na linha. Assim, até o parágrafo somente texto pode ser contado incorretamente.
buti-oxa
Minha div tem texto e imagens ... felizmente as imagens estão absolutamente posicionadas, então eu acho que elas seriam excluídas. : D De qualquer forma, eu usei o mesmo código que você sugeriu, mas com um div não um intervalo, obrigado por +1!
Albert Renshaw
20

Confira a função getClientRects () que pode ser usada para contar o número de linhas em um elemento. Aqui está um exemplo de como usá-lo.

var message_lines = $("#message_container")[0].getClientRects();

Retorna um objeto DOM javascript. A quantidade de linhas pode ser conhecida fazendo o seguinte:

var amount_of_lines = message_lines.length;

Pode retornar a altura de cada linha e muito mais. Veja a variedade completa de coisas que ele pode fazer adicionando isso ao seu script e procurando no log do console.

console.log("");
console.log("message_lines");
console.log(".............................................");
console.dir(message_lines);
console.log("");

Apesar de algumas coisas a serem observadas, ele só funciona se o elemento contido estiver embutido, no entanto, você pode colocar o elemento embutido embutido com um elemento de bloco para controlar a largura da seguinte maneira:

<div style="width:300px;" id="block_message_container">
<div style="display:inline;" id="message_container">
..Text of the post..
</div>
</div>

Embora eu não recomendo codificar o estilo assim. É apenas para fins de exemplo.

Digital-Christie
fonte
3
Esta e a outra resposta getClientRects baseada são muito melhores do que a aceita
Matteo
2
Essa deve ser a resposta aceita. Obrigado @ Digital-Christie
Antuan
12

Eu não estava satisfeito com as respostas aqui e com outras perguntas. A resposta mais alta nominal não leva paddingou borderem conta, e, portanto, obviamente, ignora box-sizingtambém. Minha resposta combina algumas técnicas aqui e em outros tópicos para obter uma solução que funcione para minha satisfação.

Não é perfeito: quando nenhum valor numérico pôde ser recuperado para line-height(por exemplo, normalou inherit), apenas usa o font-sizemultiplicado por 1.2. Talvez alguém possa sugerir uma maneira confiável de detectar o valor do pixel nesses casos.

Fora isso, ele conseguiu lidar corretamente com a maioria dos estilos e casos que eu joguei nele.

jsFiddle para brincar e testar. Também em linha abaixo.

function countLines(target) {
  var style = window.getComputedStyle(target, null);
  var height = parseInt(style.getPropertyValue("height"));
  var font_size = parseInt(style.getPropertyValue("font-size"));
  var line_height = parseInt(style.getPropertyValue("line-height"));
  var box_sizing = style.getPropertyValue("box-sizing");
  
  if(isNaN(line_height)) line_height = font_size * 1.2;
 
  if(box_sizing=='border-box')
  {
    var padding_top = parseInt(style.getPropertyValue("padding-top"));
    var padding_bottom = parseInt(style.getPropertyValue("padding-bottom"));
    var border_top = parseInt(style.getPropertyValue("border-top-width"));
    var border_bottom = parseInt(style.getPropertyValue("border-bottom-width"));
    height = height - padding_top - padding_bottom - border_top - border_bottom
  }
  var lines = Math.ceil(height / line_height);
  alert("Lines: " + lines);
  return lines;
}
countLines(document.getElementById("foo"));
div
{
  padding:100px 0 10% 0;
  background: pink;
  box-sizing: border-box;
  border:30px solid red;
}
<div id="foo">
x<br>
x<br>
x<br>
x<br>
</div>

Jeff
fonte
post útil ... Obrigado
Sinto
Boa consideração, mas no meu caso, a altura de uma linha div é superior à altura da linha em 1px, sem preenchimento e borda. Portanto, obter a altura da linha penteando sua resposta e a resposta do @ e-info128 (método cloneNode) pode ser uma escolha melhor #
Eric
8

Clone o objeto do contêiner e escreva 2 letras e calcule a altura. Isso retorna a altura real com todos os estilos aplicados, altura da linha, etc. Agora, calcule o objeto de altura / o tamanho de uma letra. No Jquery, a altura excede o preenchimento, a margem e a borda, é ótimo calcular a altura real de cada linha:

other = obj.clone();
other.html('a<br>b').hide().appendTo('body');
size = other.height() / 2;
other.remove();
lines = obj.height() /  size;

Se você usar uma fonte rara com altura diferente de cada letra, isso não funcionará. Mas funciona com todas as fontes normais, como Arial, mono, quadrinhos, Verdana, etc. Teste com sua fonte.

Exemplo:

<div id="content" style="width: 100px">hello how are you? hello how are you? hello how are you?</div>
<script type="text/javascript">
$(document).ready(function(){

  calculate = function(obj){
    other = obj.clone();
    other.html('a<br>b').hide().appendTo('body');
    size = other.height() / 2;
    other.remove();
    return obj.height() /  size;
  }

  n = calculate($('#content'));
  alert(n + ' lines');
});
</script>

Resultado: 6 Lines

Funciona em todos os navegadores sem funções raras fora dos padrões.

Verifique: https://jsfiddle.net/gzceamtr/

e-info128
fonte
Antes de tudo, quero dizer muito obrigado, exatamente o que eu estava procurando. Você poderia explicar cada linha da função de cálculo. Muito obrigado antecipadamente. SYA :)
LebCit
1
para aqueles que precisam de respostas que não sejam do jQuery: clonecloneNode, hidestyle.visibility = "hidden", html('a<br>b')textContent='a\r\nb', appendTo('body')document.documentElement.appendChild, height()getBoundingClientRect().height
Eric
Desde que eu recebo o erro de 1px entre a altura da linha e altura div, eu recomendo esta resposta. Combinar isso e a resposta de @ Jeff será ainda melhor #
Eric
6

Para quem usa jQuery http://jsfiddle.net/EppA2/3/

function getRows(selector) {
    var height = $(selector).height();
    var line_height = $(selector).css('line-height');
    line_height = parseFloat(line_height)
    var rows = height / line_height;
    return Math.round(rows);
}
penkovsky
fonte
jsfiddle.net/EppA2/9 é menos preciso, mas de acordo com esta pode ser melhor apoiado por vários navegadores
Penkovsky
8
Quando o jQuery retornar "normal" de $(selector).css('line-height');, você não obterá um número dessa função.
precisa saber é o seguinte
5

Estou convencido de que agora é impossível. Foi, no entanto.

A implementação do getClientRects do IE7 fez exatamente o que eu quero. Abra esta página no IE8, tente atualizá-la, variando a largura da janela e veja como o número de linhas no primeiro elemento muda de acordo. Aqui estão as principais linhas do javascript dessa página:

var rects = elementList[i].getClientRects();
var p = document.createElement('p');
p.appendChild(document.createTextNode('\'' + elementList[i].tagName + '\' element has ' + rects.length + ' line(s).'));

Infelizmente para mim, o Firefox sempre retorna um retângulo de cliente por elemento, e o IE8 faz o mesmo agora. (A página de Martin Honnen funciona hoje porque o IE a processa na exibição compatível com o IE; pressione F12 no IE8 para reproduzir com modos diferentes.)

Isso é triste. Parece que mais uma vez a implementação literal, mas sem valor, das especificações do Firefox conquistou a útil da Microsoft. Ou sinto falta de uma situação em que o novo getClientRects pode ajudar um desenvolvedor?

buti-oxa
fonte
2
Como fora apontado por Mozilla element.getClientRects documentação, pelo menos para Inline elementos da especificação W3C faz resultado em um rect para cada linha de texto - não é o ideal, mas algo pelo menos.
Natevw 21/03/12
getClientRects (). length é a maneira correta. getComputedStyle () pode retornar valores como "herdar", "normal" e "automático".
Binyamin 02/10
4

com base na resposta de GuyPaddock acima, isso parece funcionar para mim

function getLinesCount(element) {
  var prevLH = element.style.lineHeight;
  var factor = 1000;
  element.style.lineHeight = factor + 'px';

  var height = element.getBoundingClientRect().height;
  element.style.lineHeight = prevLH;

  return Math.floor(height / factor);
}

o truque aqui é aumentar tanto a altura da linha que "engolirá" quaisquer diferenças de navegador / SO na maneira como elas renderizam fontes

Verifiquei com vários estilos e tamanhos de fonte / famílias diferentes, o que não leva em consideração (já que no meu caso não importava), é o preenchimento - que pode ser facilmente adicionado à solução.

Maor Yosef
fonte
2

Não, não de forma confiável. Existem simplesmente muitas variáveis ​​desconhecidas

  1. Qual SO (diferentes DPIs, variações de fonte, etc ...)?
  2. Eles têm o tamanho da fonte aumentado porque são praticamente cegos?
  3. Caramba, nos navegadores webkit, você pode redimensionar as caixas de texto conforme o desejo do seu coração.

A lista continua. Algum dia, espero que exista um método desse tipo para realizar isso de forma confiável com JavaScript, mas até esse dia chegar, você estará sem sorte.

Eu odeio esse tipo de resposta e espero que alguém possa me provar que estou errado.

KyleFarris
fonte
3
Se você contar as linhas após a renderização, todas essas incógnitas serão irrelevantes. Seu argumento se aplica, é claro, se você tentar "estimar" quantas linhas o navegador usa ao renderizar o texto.
simon
Eu diria que a variável de usuários que aumenta o zoom e / ou altera o tamanho do texto pode ser ignorada. Nunca vi um site com design otimizado para acomodar o dimensionamento de janelas (após o carregamento da página) ou a alteração do tamanho do texto no navegador, por isso não acho que sejam fatores importantes nessa questão específica. No entanto, eu concordo que não há soluções confiáveis, apenas as "estimativas de estimativa" baseadas em pixels, como outras pessoas apontaram.
Kalko
2

Você deve poder dividir ('\ n'). Length e obter as quebras de linha.

update: funciona no FF / Chrome, mas não no IE.

<html>
<head>
<script src="jquery-1.3.2.min.js"></script>
<script>
    $(document).ready(function() {
        var arr = $("div").text().split('\n');
        for (var i = 0; i < arr.length; i++)
            $("div").after(i + '=' + arr[i] + '<br/>');
    });
</script>
</head>
<body>
<div>One
Two
Three</div>
</body>
</html>
Chad Grant
fonte
1
Experimente e deixe-nos saber como isso acontece. Estou falando sério, não sendo sarcástico. Você pode usar esse código no console do FireBug nesta mesma página. var the_text = $ ('. welovestackoverflow p'). text (); var numlines = the_text.split ("\ n"). length; alerta (linhas numéricas);
KyleFarris
3
Obrigado por esta surpresa, eu não sabia que era possível, mas não é isso que eu quero. Jquery parece contar quebras de linha rígida na fonte e estou interessado em quebras de linha automáticas no resultado. No caso da sua div "One Two Three", o resultado deve ser 1, porque o navegador coloca todo o texto em uma linha.
buti-oxa
Então eu entendi mal sua pergunta. Você deseja encontrar a altura da linha calculada então.
Chad Grant
1

getClientRectsdevolver os rects clientes como este e se você quiser obter as linhas, use a função follow 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;
}
Defims
fonte
1

Eu encontrei uma maneira de calcular o número da linha quando desenvolvo um editor de html. O método principal é que:

  1. No IE, você pode chamar getBoundingClientRects, ele retorna cada linha como um retângulo

  2. No webkit ou no novo mecanismo html padrão, ele retorna os retângulos de cada elemento ou nó do nó; nesse caso, você pode comparar cada retângulo, quero dizer que cada um deve ter um retângulo maior, portanto, você pode ignorar aqueles retângulos cuja altura é menor (se existe a parte superior de um retângulo menor que a parte inferior e a parte inferior maior, a condição é verdadeira.)

então vamos ver o resultado do teste:

insira a descrição da imagem aqui

O retângulo verde é o maior retângulo em cada linha

O retângulo vermelho é o limite de seleção

O retângulo azul é o limite do início à seleção após a expansão; vemos que ele pode ser maior que o retângulo vermelho; portanto, precisamos verificar a parte inferior de cada retângulo para limitar que deve ser menor que a parte inferior do retângulo vermelho.

        var lineCount = "?";
        var rects;
        if (window.getSelection) {
            //Get all client rectangles from body start to selection, count those rectangles that has the max bottom and min top
            var bounding = {};
            var range = window.getSelection().getRangeAt(0);//As this is the demo code, I dont check the range count
            bounding = range.getBoundingClientRect();//!!!GET BOUNDING BEFORE SET START!!!

            //Get bounding and fix it , when the cursor is in the last character of lineCount, it may expand to the next lineCount.
            var boundingTop = bounding.top;
            var boundingBottom = bounding.bottom;
            var node = range.startContainer;
            if (node.nodeType !== 1) {
                node = node.parentNode;
            }
            var style = window.getComputedStyle(node);
            var lineHeight = parseInt(style.lineHeight);
            if (!isNaN(lineHeight)) {
                boundingBottom = boundingTop + lineHeight;
            }
            else {
                var fontSize = parseInt(style.fontSize);
                if (!isNaN(fontSize)) {
                    boundingBottom = boundingTop + fontSize;
                }
            }
            range = range.cloneRange();

            //Now we have enougn datas to compare

            range.setStart(body, 0);
            rects = range.getClientRects();
            lineCount = 0;
            var flags = {};//Mark a flags to avoid of check some repeat lines again
            for (var i = 0; i < rects.length; i++) {
                var rect = rects[i];
                if (rect.width === 0 && rect.height === 0) {//Ignore zero rectangles
                    continue;
                }
                if (rect.bottom > boundingBottom) {//Check if current rectangle out of the real bounding of selection
                    break;
                }
                var top = rect.top;
                var bottom = rect.bottom;
                if (flags[top]) {
                    continue;
                }
                flags[top] = 1;

                //Check if there is no rectangle contains this rectangle in vertical direction.
                var succ = true;
                for (var j = 0; j < rects.length; j++) {
                    var rect2 = rects[j];
                    if (j !== i && rect2.top < top && rect2.bottom > bottom) {
                        succ = false;
                        break;
                    }
                }
                //If succ, add lineCount 1
                if (succ) {
                    lineCount++;
                }
            }
        }
        else if (editor.document.selection) {//IN IE8 getClientRects returns each single lineCount as a rectangle
            var range = body.createTextRange();
            range.setEndPoint("EndToEnd", range);
            rects = range.getClientRects();
            lineCount = rects.length;
        }
        //Now we get lineCount here
dexiang
fonte
1

Experimente esta solução:

function calculateLineCount(element) {
  var lineHeightBefore = element.css("line-height"),
      boxSizing        = element.css("box-sizing"),
      height,
      lineCount;

  // Force the line height to a known value
  element.css("line-height", "1px");

  // Take a snapshot of the height
  height = parseFloat(element.css("height"));

  // Reset the line height
  element.css("line-height", lineHeightBefore);

  if (boxSizing == "border-box") {
    // With "border-box", padding cuts into the content, so we have to subtract
    // it out
    var paddingTop    = parseFloat(element.css("padding-top")),
        paddingBottom = parseFloat(element.css("padding-bottom"));

    height -= (paddingTop + paddingBottom);
  }

  // The height is the line count
  lineCount = height;

  return lineCount;
}

Você pode vê-lo em ação aqui: https://jsfiddle.net/u0r6avnt/

Tente redimensionar os painéis da página (para aumentar ou diminuir o lado direito da página) e, em seguida, execute-o novamente para verificar se é possível dizer com confiança quantas linhas existem.

Esse problema é mais difícil do que parece, mas a maior parte da dificuldade vem de duas fontes:

  1. A renderização de texto é de nível muito baixo nos navegadores para ser consultada diretamente no JavaScript. Mesmo o pseudo-seletor CSS :: first-line não se comporta exatamente como outros seletores (você não pode invertê-lo, por exemplo, para aplicar estilo a todos, exceto à primeira linha).

  2. O contexto desempenha um papel importante na maneira como você calcula o número de linhas. Por exemplo, se a altura da linha não foi definida explicitamente na hierarquia do elemento de destino, você pode obter "normal" de volta como altura da linha. Além disso, o elemento pode estar sendo usado box-sizing: border-boxe, portanto, sujeito a preenchimento.

Minha abordagem minimiza o número 2, assumindo o controle da altura da linha diretamente e considerando o método de dimensionamento da caixa, levando a um resultado mais determinístico.

GuyPaddock
fonte
1

Em certos casos, como um link que se estende por várias linhas em texto não justificado, você pode obter a contagem de linhas e todas as coordenadas de cada linha ao usar isso:

var rectCollection = object.getClientRects();

https://developer.mozilla.org/en-US/docs/Web/API/Element/getClientRects

Isso funciona porque cada linha seria diferente, mesmo que ligeiramente. Contanto que sejam, eles são desenhados como um "retângulo" diferente pelo renderizador.

Firsh - LetsWP.io
fonte
0

Seguindo a sugestão do @BobBrunius 2010, criei isso com o jQuery. Sem dúvida, poderia ser melhorado, mas pode ajudar alguns.

$(document).ready(function() {

  alert("Number of lines: " + getTextLinesNum($("#textbox")));

});

function getTextLinesNum($element) {

  var originalHtml = $element.html();
  var words = originalHtml.split(" ");
  var linePositions = [];
        
  // Wrap words in spans
  for (var i in words) {
    words[i] = "<span>" + words[i] + "</span>";
  }
        
  // Temporarily replace element content with spans. Layout should be identical.
  $element.html(words.join(" "));
        
  // Iterate through words and collect positions of text lines
  $element.children("span").each(function () {
    var lp = $(this).position().top;
    if (linePositions.indexOf(lp) == -1) linePositions.push(lp);
  });
        
  // Revert to original html content
  $element.html(originalHtml);
        
  // Return number of text lines
  return linePositions.length;

}
#textbox {
  width: 200px;
  text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div id="textbox">Lorem ipsum dolor sit amet, consectetuer adipiscing elit,
  <br>sed diam nonummy</div>

josef
fonte
0

Você pode comparar a altura e a altura do elemento com line-height: 0

function lineCount(elm) {
  const style = elm.getAttribute('style')
  elm.style.marginTop = 0
  elm.style.marginBottom = 0
  elm.style.paddingTop = 0
  elm.style.paddingBottom = 0
  const heightAllLine = elm.offsetHeight
  elm.style.lineHeight = 0
  const height1line = elm.offsetHeight
  const lineCount = Math.round(heightAllLine / height1line)
  elm.setAttribute('style', style)
  if (isNaN(lineCount)) return 0
  return lineCount
}
Yukulélé
fonte