¿Cómo puedo contar líneas de texto dentro de un elemento DOM? ¿Puedo?

127

Me pregunto si hay una manera de contar líneas dentro de un div, por ejemplo. Digamos que tenemos un div como este:

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

Dependiendo de muchos factores, el div puede tener una, dos o incluso cuatro líneas de texto. ¿Hay alguna forma de que el script lo sepa?

En otras palabras, ¿los saltos automáticos están representados en DOM?

buti-oxa
fuente

Respuestas:

89

Si el tamaño del div depende del contenido (que supongo que es el caso de su descripción), puede recuperar la altura del div usando:

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

Y dividir por la altura de la línea de fuente:

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

O para obtener la altura de la línea si no se ha establecido explícitamente:

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

También deberá tener en cuenta el relleno y el espaciado entre líneas.

EDITAR

Prueba totalmente autónoma, estableciendo explícitamente la altura de línea:

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
fuente
77
Es un buen comienzo, excepto que la altura de la línea no siempre se ha establecido. Debería obtener eso del estilo calculado del elemento.
Chetan S
Pensándolo bien, la altura de la línea no siempre debe ser numérica (o en píxeles). Entonces, si su código se basa en él y su convención CSS lo permite, probablemente debería establecer una altura de línea en píxeles.
Chetan S
66
@Chetan: establecer dimensiones de texto en píxeles generalmente se considera algo malo. astahost.com/Sizes-Webdesign-Em-Vs-Px-t8926.html
annakata
66
Esto solo puede funcionar en el caso más simple (como mi ejemplo). Si hay espacios en el interior, elementos de bloque en línea, etc., la división directa por tamaño de fuente (principal) no tiene valor. Aún así, es mejor que nada, gracias.
buti-oxa
2
Creo que un mejor método para obtener un cálculo line-height(que no se establece explícitamente) sería usarwindow.getComputedStyle(element, null).getPropertyValue('line-height')
rnevius
20

Una solución es encerrar cada palabra en una etiqueta span usando un script. Entonces, si la dimensión Y de una etiqueta de extensión dada es menor que la de su predecesor inmediato, se ha producido un salto de línea.

Bob Brunius
fuente
¡Inteligente! ¿Cómo haces eso? Supongo que supones un párrafo de solo texto (como lo hice en el ejemplo). No tenía en mente esta limitación, pero puede estar bien, siempre que el script no se bloquee cuando hay algo más dentro del párrafo.
buti-oxa
1
Pensándolo bien, este método falla si hay tramos alineados verticalmente en la línea. Entonces, incluso el párrafo de solo texto puede contarse incorrectamente.
buti-oxa
Mi div tiene texto e imágenes ... afortunadamente, las imágenes están en una posición absoluta, así que creo que serían excluidas. : D De todos modos, utilicé el mismo código que sugirió pero con un div no un lapso, ¡gracias aunque +1!
Albert Renshaw
20

Consulte la función getClientRects () que se puede usar para contar el número de líneas en un elemento. Aquí hay un ejemplo de cómo usarlo.

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

Devuelve un objeto DOM de JavaScript. La cantidad de líneas se puede conocer haciendo esto:

var amount_of_lines = message_lines.length;

Puede devolver la altura de cada línea, y más. Vea toda la gama de cosas que puede hacer agregando esto a su script y luego buscando en el registro de su consola.

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

Aunque algunas cosas a tener en cuenta es que solo funciona si el elemento contenedor está en línea, sin embargo, puede rodear el elemento en línea contenedor con un elemento de bloque para controlar el ancho de esta manera:

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

Aunque no recomiendo codificar el estilo así. Es solo por ejemplo.

Digital-Christie
fuente
3
Esta y la otra respuesta basada en getClientRects son mucho mejores que la respuesta aceptada
matteo
2
Esta debería ser la respuesta aceptada. Gracias @ Digital-Christie
Antuan
12

No estaba satisfecho con las respuestas aquí y con otras preguntas. La respuesta mejor calificada no tiene en cuenta paddingo border, por lo tanto, obviamente ignorabox-sizing . Mi respuesta combina algunas técnicas aquí y en otros hilos para obtener una solución que funcione a mi entera satisfacción.

No es perfecto: cuando no se pudo recuperar ningún valor numérico para line-height(por ejemplo, normalor inherit), solo utiliza el font-sizemultiplicado por 1.2. Quizás alguien más pueda sugerir una forma confiable de detectar el valor de píxel en esos casos.

Aparte de eso, ha podido manejar correctamente la mayoría de los estilos y casos que le he presentado.

jsFiddle para jugar y probar. También en línea a continuación.

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
fuente
útil post ... Gracias
Sinto
Buena consideración, pero en mi caso la altura de una línea div es más que la altura de la línea por 1px, sin relleno ni borde. Entonces, obtener la altura de la línea combinando su respuesta y la respuesta de @ e-info128 (método cloneNode) puede ser una mejor opción
Eric
8

Clone el objeto contenedor y escriba 2 letras y calcule la altura. Esto devuelve la altura real con todo el estilo aplicado, altura de línea, etc. Ahora, calcule el objeto de altura / el tamaño de una letra. En Jquery, la altura excede el relleno, el margen y el borde, es genial calcular la altura real de cada línea:

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

Si usa una fuente rara con diferente altura de cada letra, esto no funciona. Pero funciona con todas las fuentes normales, como Arial, mono, comics, Verdana, etc. Pruebe con su fuente.

Ejemplo:

<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 en todos los navegadores sin funciones raras fuera de los estándares.

Verificación: https://jsfiddle.net/gzceamtr/

e-info128
fuente
En primer lugar, quiero decir muchas gracias, justo lo que estaba buscando. ¿Podría explicar cada línea de la función de cálculo? Muchas gracias por adelantado. SYA :)
LebCit
1
para aquellos que necesitan una respuesta que no sea jQuery: clonecloneNode, hidestyle.visibility = "hidden", html('a<br>b')textContent='a\r\nb', appendTo('body')document.documentElement.appendChild, height()getBoundingClientRect().height
Eric
Como recibo un error de 1 px entre la altura de la línea y la altura div, recomiendo esta respuesta. Combinar esto y la respuesta de @ Jeff será aún mejor
Eric
6

Para aquellos que usan 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
fuente
jsfiddle.net/EppA2/9 es menos preciso, pero de acuerdo con esto puede ser mejor soportado por varios navegadores
penkovsky
8
Cuando jQuery devuelve "normal" de $(selector).css('line-height');, no obtendrá un número de esa función.
bafromca
5

Estoy convencido de que ahora es imposible. Fue, sin embargo.

La implementación de getClientRects de IE7 hizo exactamente lo que quiero. Abra esta página en IE8, intente actualizarla variando el ancho de la ventana y vea cómo el número de líneas en el primer elemento cambia en consecuencia. Aquí están las líneas clave del javascript de esa 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).'));

Desafortunadamente para mí, Firefox siempre devuelve un rectángulo de cliente por elemento, e IE8 hace lo mismo ahora. (La página de Martin Honnen funciona hoy porque IE la muestra en la vista de compatibilidad de IE; presione F12 en IE8 para jugar con diferentes modos).

Esto es triste. Parece que una vez más, la implementación literal pero inútil de Firefox de la especificación ganó sobre la útil de Microsoft. ¿O echo de menos una situación en la que los nuevos getClientRects pueden ayudar a un desarrollador?

buti-oxa
fuente
2
Como en punta a cabo por Mozilla element.getClientRects documentación, al menos para Inline elementos de la especificación del W3C hace resultado en un rect para cada línea de texto - no es ideal, pero algo por lo menos.
natevw
getClientRects (). length es la forma correcta. getComputedStyle () puede devolver valores como "heredar", "normal" y "auto".
Binyamin
4

basado en la respuesta de GuyPaddock de arriba, esto parece funcionar para mí

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

el truco aquí es aumentar la altura de la línea tanto que "tragará" cualquier diferencia de navegador / sistema operativo en la forma en que representan las fuentes

Comprobado con varios estilos y diferentes tamaños de fuente / familias, lo único que no tiene en cuenta (ya que en mi caso no importó) es el relleno, que se puede agregar fácilmente a la solución.

Maor Yosef
fuente
2

No, no de manera confiable. Simplemente hay demasiadas variables desconocidas

  1. ¿Qué sistema operativo (diferentes DPI, variaciones de fuente, etc.)?
  2. ¿Tienen su tamaño de fuente ampliado porque son prácticamente ciegos?
  3. Diablos, en los navegadores webkit, puedes cambiar el tamaño de los cuadros de texto según lo desees.

La lista continua. Algún día espero que haya un método para lograr esto de manera confiable con JavaScript, pero hasta que llegue ese día, no tendrás suerte.

Odio este tipo de respuestas y espero que alguien pueda demostrar que estoy equivocado.

KyleFarris
fuente
3
Si cuenta las líneas después de renderizar, todas esas incógnitas son irrelevantes. Su punto se aplica, por supuesto, si intenta "adivinar" cuántas líneas usa el navegador al representar el texto.
Simon
Diría que se puede ignorar la variable de usuarios que amplían o cambian el tamaño del texto. Nunca he visto un sitio que tenga su diseño optimizado para adaptarse a la escala de la ventana (después de cargar la página) o cambiar el tamaño del texto en el navegador, por lo que no creo que esos sean factores importantes en esta pregunta específica. Sin embargo, estoy de acuerdo en que no hay soluciones confiables, solo las "conjeturas" basadas en píxeles, como señalaron otras personas.
Kalko
2

Debería poder dividir ('\ n'). Length y obtener los saltos de línea.

actualización: esto funciona en FF / Chrome pero no en 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
fuente
1
Pruébalo y cuéntanos cómo te va. Estoy hablando en serio, no siendo sarcástico. Puede usar este código en la consola de FireBug en esta misma página. var the_text = $ ('. welovestackoverflow p'). text (); var numlines = the_text.split ("\ n"). length; alerta (líneas numéricas);
KyleFarris el
3
Gracias por esta sorpresa, no sabía que era posible, pero esto no es lo que quiero. Jquery parece contar saltos de línea duros en la fuente, y estoy interesado en los saltos de línea automáticos en el resultado. En el caso de su div "One Two Three", el resultado debería ser 1, porque el navegador coloca todo el texto en una línea.
buti-oxa
Entonces entendí mal tu pregunta. Desea encontrar la altura de línea calculada entonces.
Chad Grant
1

getClientRectsdevuelva el cliente de esta manera y si desea obtener las líneas, use la siguiente función de esta manera

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
fuente
1

Encontré una forma de calcular el número de línea cuando desarrollo un editor html. El método principal es que:

  1. En IE puede llamar a getBoundingClientRects, devuelve cada línea como un rectángulo

  2. En webkit o en el nuevo motor html estándar, devuelve cada elemento o rectángulos de cliente de nodo, en este caso puede comparar cada rectángulo, quiero decir que cada uno debe ser un rectángulo es el más grande, por lo que puede ignorar esos rectángulos cuya altura es menor (si hay una parte superior de un rectángulo más pequeña que ella y una parte inferior más grande que ella, la condición es verdadera).

así que veamos el resultado de la prueba:

ingrese la descripción de la imagen aquí

El rectángulo verde es el rectángulo más grande de cada fila.

El rectángulo rojo es el límite de selección.

El rectángulo azul es el límite desde el inicio hasta la selección después de expandirse, vemos que puede ser más grande que el rectángulo rojo, por lo que debemos verificar el fondo de cada rectángulo para limitarlo debe ser más pequeño que el fondo del rectángulo rojo.

        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
fuente
1

Prueba esta solución:

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

Puedes verlo en acción aquí: https://jsfiddle.net/u0r6avnt/

Intente cambiar el tamaño de los paneles en la página (para hacer que el lado derecho de la página sea más ancho o más corto) y luego ejecútelo nuevamente para ver que puede decir de manera confiable cuántas líneas hay.

Este problema es más difícil de lo que parece, pero la mayor parte de la dificultad proviene de dos fuentes:

  1. La representación de texto es de nivel demasiado bajo en los navegadores para ser consultado directamente desde JavaScript. Incluso el pseudo-selector CSS :: primera línea no se comporta como lo hacen otros selectores (no puede invertirlo, por ejemplo, para aplicar estilo a todos menos a la primera línea).

  2. El contexto juega un papel importante en la forma de calcular el número de líneas. Por ejemplo, si la altura de línea no se estableció explícitamente en la jerarquía del elemento de destino, es posible que vuelva a ser "normal" como altura de línea. Además, el elemento podría estar utilizando box-sizing: border-boxy, por lo tanto, estar sujeto a relleno.

Mi enfoque minimiza el n. ° 2 tomando el control de la altura de la línea directamente y factorizando el método de dimensionamiento de la caja, lo que lleva a un resultado más determinista.

GuyPaddock
fuente
1

En ciertos casos, como un enlace que abarca varias filas en texto no justificado, puede obtener el recuento de filas y cada coordenada de cada línea, cuando usa esto:

var rectCollection = object.getClientRects();

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

Esto funciona porque cada línea sería diferente aunque sea ligeramente. Mientras lo sean, el renderizador los dibujará como un "rectángulo" diferente.

Firsh - LetsWP.io
fuente
0

Siguiendo la sugerencia de @BobBrunius 2010, creé esto con jQuery. Sin duda podría mejorarse, pero puede ayudar a algunos.

$(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
fuente
0

Puede comparar la altura del elemento y la altura del elemento con 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é
fuente