Comment compter les lignes de texte dans un élément DOM? Puis-je?

127

Je me demande s'il existe un moyen de compter les lignes à l'intérieur d'un div par exemple. Disons que nous avons un div comme ceci:

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

Selon de nombreux facteurs, le div peut avoir une, deux ou même quatre lignes de texte. Y a-t-il un moyen pour le script de savoir?

En d'autres termes, les pauses automatiques sont-elles représentées dans le DOM?

buti-oxa
la source

Réponses:

89

Si la taille du div dépend du contenu (ce que je suppose être le cas d'après votre description), vous pouvez récupérer la hauteur du div en utilisant:

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

Et divisez par la hauteur de la ligne de police:

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

Ou pour obtenir la hauteur de ligne si elle n'a pas été explicitement définie:

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

Vous devrez également prendre en compte le remplissage et l'espacement inter-lignes.

ÉDITER

Test entièrement autonome, définissant explicitement la hauteur de ligne:

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>

bassin
la source
7
C'est un bon début, sauf que la hauteur de ligne n'a peut-être pas toujours été définie. Vous devriez obtenir cela à partir du style calculé de l'élément.
Chetan S
À la réflexion, la hauteur de ligne ne doit pas toujours être numérique (ou en pixels). Donc, si votre code en dépend et que votre convention CSS le permet, vous devriez probablement définir une hauteur de ligne en pixels.
Chetan S
6
@Chetan - définir les dimensions du texte en pixels est généralement considéré comme une mauvaise chose. astahost.com/Sizes-Webdesign-Em-Vs-Px-t8926.html
annakata
6
Cela ne peut fonctionner que dans les cas les plus simples (comme mon exemple). S'il y a des étendues à l'intérieur, des éléments de blocs en ligne et ainsi de suite, une division simple par la taille de police (parent) est sans valeur. Pourtant, c'est mieux que rien, merci.
buti-oxa
2
Je pense qu'une meilleure méthode pour obtenir un calcul line-height(qui n'est pas explicitement défini) serait d'utiliserwindow.getComputedStyle(element, null).getPropertyValue('line-height')
rnevius
20

Une solution consiste à inclure chaque mot dans une balise span à l'aide d'un script. Ensuite, si la dimension Y d'une balise span donnée est inférieure à celle de son prédécesseur immédiat, un saut de ligne s'est produit.

Bob Brunius
la source
Intelligent! Comment tu fais ça? Je suppose que vous supposez que le texte n'est qu'un paragraphe (comme je l'ai dans l'exemple). Je n'avais pas cette limitation à l'esprit, mais cela peut être correct, à condition que le script ne plante pas lorsqu'il y a autre chose à l'intérieur du paragraphe.
buti-oxa
1
Après réflexion, cette méthode échoue s'il y a des travées alignées verticalement dans la ligne. Ainsi, même les paragraphes contenant uniquement du texte peuvent être mal comptés.
buti-oxa du
Ma div a du texte et des images ... heureusement, les images sont absolument positionnées donc je pense qu'elles seraient exclues. : D Quoi qu'il en soit, j'ai utilisé le même code que vous avez suggéré mais avec un div pas un span, merci cependant +1!
Albert Renshaw
20

Découvrez la fonction getClientRects () qui peut être utilisée pour compter le nombre de lignes dans un élément. Voici un exemple de son utilisation.

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

Il renvoie un objet DOM javascript. Le nombre de lignes peut être connu en faisant ceci:

var amount_of_lines = message_lines.length;

Il peut renvoyer la hauteur de chaque ligne, et plus encore. Découvrez la gamme complète de choses qu'il peut faire en l'ajoutant à votre script, puis en consultant le journal de votre console.

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

Bien que cela ne fonctionne que si l'élément conteneur est en ligne, vous pouvez cependant entourer l'élément en ligne conteneur avec un élément bloc pour contrôler la largeur comme suit:

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

Bien que je ne recommande pas de coder en dur le style comme ça. C'est juste à titre d'exemple.

Digital-Christie
la source
3
Ceci et les autres réponses basées sur getClientRects sont bien meilleures que celles acceptées
matteo
2
Cela devrait être la réponse acceptée. Merci @ Digital-Christie
Antuan
12

Je n'étais pas satisfait des réponses ici et sur d'autres questions. La réponse la mieux notée ne prend pas en compte paddingou ne prend pas borderen compte, et donc de toute évidence l'ignore box-sizingégalement. Ma réponse combine quelques techniques ici et et sur d'autres threads pour obtenir une solution qui fonctionne à ma satisfaction.

Ce n'est pas parfait: lorsqu'aucune valeur numérique n'a pu être récupérée pour le line-height(par exemple normalou inherit), il utilise simplement le font-sizemultiplié par 1.2. Peut-être que quelqu'un d'autre peut suggérer un moyen fiable de détecter la valeur du pixel dans ces cas.

En dehors de cela, il a été capable de gérer correctement la plupart des styles et des cas que je lui ai lancés.

jsFiddle pour jouer et tester. Également en ligne ci-dessous.

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
la source
article utile ... Merci
Sinto
Bonne considération, mais dans mon cas, la hauteur d'une ligne div est supérieure à la hauteur de ligne de 1px, sans aucun remplissage ni bordure. Donc, obtenir la hauteur de ligne en combinant votre réponse et la réponse de @ e-info128 (méthode cloneNode) peut être un meilleur choix
Eric
8

Clonez l'objet conteneur et écrivez 2 lettres et calculez la hauteur. Cela renvoie la hauteur réelle avec tout le style appliqué, la hauteur de la ligne, etc. Maintenant, calculez la hauteur de l'objet / la taille d'une lettre. Dans Jquery, la hauteur dépasse le remplissage, la marge et la bordure, il est bon de calculer la hauteur réelle de chaque ligne:

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

Si vous utilisez une police rare avec une hauteur différente de chaque lettre, cela ne fonctionne pas. Mais fonctionne avec toutes les polices normales, comme Arial, mono, bandes dessinées, Verdana, etc. Testez avec votre police.

Exemple:

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

Résultat: 6 Lines

Fonctionne dans tous les navigateurs sans fonctions rares hors normes.

Vérifiez: https://jsfiddle.net/gzceamtr/

e-info128
la source
Tout d'abord, je tiens à vous remercier beaucoup, exactement ce que je recherchais. Pourriez-vous s'il vous plaît expliquer chaque ligne de la fonction de calcul. Merci beaucoup d'avance. SYA :)
LebCit
1
pour ceux qui ont besoin d'une réponse non-jQuery: clonecloneNode, hidestyle.visibility = "hidden", html('a<br>b')textContent='a\r\nb', appendTo('body')document.documentElement.appendChild, height()getBoundingClientRect().height
Eric
Puisque j'obtiens une erreur de 1px entre la hauteur de la ligne et la hauteur div, je recommande cette réponse. Combiner cela et la réponse de @ Jeff sera encore mieux
Eric
6

Pour ceux qui utilisent 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
la source
jsfiddle.net/EppA2/9 est moins précis, mais selon cela, il pourrait être mieux pris en charge par divers navigateurs
penkovsky
8
Lorsque jQuery retourne "normal" à partir de $(selector).css('line-height');, vous n'obtiendrez pas un nombre de cette fonction.
bafromca
5

Je suis convaincu que c'est impossible maintenant. C'était, cependant.

L'implémentation de getClientRects par IE7 a fait exactement ce que je voulais. Ouvrez cette page dans IE8, essayez de l'actualiser en variant la largeur de la fenêtre et voyez comment le nombre de lignes dans le premier élément change en conséquence. Voici les principales lignes du javascript de cette page:

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

Malheureusement pour moi, Firefox renvoie toujours un rectangle client par élément, et IE8 fait de même maintenant. (La page de Martin Honnen fonctionne aujourd'hui car IE la rend en vue compatible IE; appuyez sur F12 dans IE8 pour jouer avec différents modes.)

C'est triste. Il semble qu'une fois de plus l'implémentation littérale mais sans valeur de Firefox de la spécification a gagné sur l'utile de Microsoft. Ou est-ce que je rate une situation où de nouveaux getClientRects peuvent aider un développeur?

buti-oxa
la source
2
Comme en pointe par Mozilla element.getClientRects la documentation, au moins pour inline éléments de la spécification du W3C ne résulte en une rect pour chaque ligne de texte - pas idéal, mais quelque chose au moins.
natevw
getClientRects (). length est le bon moyen. getComputedStyle () peut renvoyer des valeurs telles que "inherit", "normal" et "auto".
Binyamin
4

basé sur la réponse de GuyPaddock d'en haut, cela semble fonctionner pour moi

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

l'astuce ici est d'augmenter tellement la hauteur de la ligne qu'elle "engloutira" toutes les différences de navigateur / système d'exploitation dans la façon dont ils rendent les polices

Vérifié avec différents styles et différentes tailles / familles de polices, la seule chose qu'il ne prend pas en compte (car dans mon cas, cela n'avait pas d'importance), c'est le rembourrage - qui peut facilement être ajouté à la solution.

Maor Yosef
la source
2

Non, pas de manière fiable. Il y a tout simplement trop de variables inconnues

  1. Quel OS (différents DPI, variations de polices, etc ...)?
  2. La taille de leur police a-t-elle été augmentée parce qu’elle est pratiquement aveugle?
  3. Heck, dans les navigateurs Webkit, vous pouvez en fait redimensionner les zones de texte à votre guise.

La liste continue. Un jour, j'espère qu'il y aura une telle méthode pour accomplir cela de manière fiable avec JavaScript, mais jusqu'à ce que ce jour arrive, vous n'avez pas de chance.

Je déteste ce genre de réponses et j'espère que quelqu'un pourra me prouver le contraire.

KyleFarris
la source
3
Si vous comptez les lignes après le rendu, toutes ces inconnues ne sont pas pertinentes. Votre remarque s'applique, bien sûr, si vous essayez «d'estimer» le nombre de lignes utilisées par le navigateur lors du rendu du texte.
simon
Je dirais que la variable des utilisateurs zoomant et / ou modifiant la taille du texte peut être ignorée. Je n'ai jamais vu un site dont la conception est optimisée pour s'adapter à la mise à l'échelle de la fenêtre (après le chargement de la page) ou à la modification de la taille du texte au niveau du navigateur, donc je ne pense pas que ce soient des facteurs importants dans cette question spécifique. Cependant, je suis d'accord qu'il n'y a pas de solutions fiables, juste les "estimations" basées sur les pixels comme d'autres personnes l'ont souligné.
Kalko
2

Vous devriez pouvoir diviser ('\ n'). Length et obtenir les sauts de ligne.

mise à jour: cela fonctionne sur FF / Chrome mais pas 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
la source
1
Essayez-le et dites-nous comment cela se passe. Je suis très sérieux, je ne suis pas sarcastique. Vous pouvez utiliser ce code dans la console de FireBug sur cette même page. var the_text = $ ('. welovestackoverflow p'). text (); var numlines = the_text.split ("\ n"). length; alerte (numlines);
KyleFarris
3
Merci pour cette surprise, je ne savais pas que c'était possible, mais ce n'est pas ce que je veux. Jquery semble compter les sauts de ligne durs dans la source, et je suis intéressé par les sauts de ligne automatiques dans le résultat. Dans le cas de votre div "One Two Three", le résultat doit être 1, car le navigateur met tout le texte sur une seule ligne.
buti-oxa
Ensuite, j'ai mal compris votre question. Vous voulez alors trouver la hauteur de ligne calculée.
Chad Grant
1

getClientRectsretourne les rects client comme ceci et si vous voulez obtenir les lignes, utilisez la fonction suivante comme ceci

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
la source
1

J'ai trouvé un moyen de calculer le numéro de ligne lorsque je développe un éditeur html. La méthode principale est la suivante:

  1. Dans IE, vous pouvez appeler getBoundingClientRects, il renvoie chaque ligne sous forme de rectangle

  2. Dans le webkit ou le nouveau moteur html standard, il renvoie les rectangles clients de chaque élément ou nœud, dans ce cas, vous pouvez comparer chaque rectangle, je veux dire que chaque rectangle doit être le plus grand, vous pouvez donc ignorer ces rectangles dont la hauteur est plus petite (si le haut du rectangle est plus petit que lui et le bas plus grand que lui, la condition est vraie.)

voyons donc le résultat du test:

entrez la description de l'image ici

Le rectangle vert est le plus grand rectangle de chaque ligne

Le rectangle rouge est la limite de sélection

Le rectangle bleu est la limite du début à la sélection après l'expansion, nous voyons qu'il peut être plus grand que le rectangle rouge, nous devons donc vérifier le bas de chaque rectangle pour limiter qu'il doit être plus petit que le bas du rectangle rouge.

        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
la source
1

Essayez cette solution:

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

Vous pouvez le voir en action ici: https://jsfiddle.net/u0r6avnt/

Essayez de redimensionner les panneaux de la page (pour élargir ou raccourcir le côté droit de la page), puis réexécutez-le pour voir qu'il peut indiquer de manière fiable le nombre de lignes.

Ce problème est plus difficile qu'il n'y paraît, mais la plupart des difficultés proviennent de deux sources:

  1. Le rendu du texte est trop bas dans les navigateurs pour être directement interrogé à partir de JavaScript. Même le pseudo-sélecteur CSS :: first-line ne se comporte pas tout à fait comme les autres sélecteurs (vous ne pouvez pas l'inverser, par exemple, pour appliquer un style à tous sauf à la première ligne).

  2. Le contexte joue un grand rôle dans la façon dont vous calculez le nombre de lignes. Par exemple, si la hauteur de ligne n'a pas été explicitement définie dans la hiérarchie de l'élément cible, vous pouvez retrouver "normal" en tant que hauteur de ligne. De plus, l'élément peut utiliser box-sizing: border-boxet donc être soumis à un rembourrage.

Mon approche minimise # 2 en prenant le contrôle de la hauteur de ligne directement et en prenant en compte la méthode de dimensionnement de la boîte, conduisant à un résultat plus déterministe.

GuyPaddock
la source
1

Dans certains cas, comme un lien s'étendant sur plusieurs lignes dans un texte non justifié, vous pouvez obtenir le nombre de lignes et toutes les coordonnées de chaque ligne, lorsque vous utilisez ceci:

var rectCollection = object.getClientRects();

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

Cela fonctionne parce que chaque ligne serait différente même si légèrement. Tant qu'ils le sont, ils sont dessinés comme un "rectangle" différent par le moteur de rendu.

Firsh - LetsWP.io
la source
0

Suite à la suggestion de @BobBrunius 2010, j'ai créé ceci avec jQuery. Cela pourrait sans doute être amélioré, mais cela peut en aider certains.

$(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
la source
0

Vous pouvez comparer la hauteur de l'élément et la hauteur de l'élément avec 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é
la source