[javascript] How can I count text lines inside an DOM element? Can I?

I'm wondering if there's a way to count lines inside a div for example. Say we have a div like so:

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

Depending on many factors, the div can have one, or two, or even four lines of text. Is there any way for the script to know?

In other words, are automatic breaks represented in DOM at all?

This question is related to javascript html dom

The answer is


getClientRects return the client rects like this and if you want to get the lines, use the follow function like this

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

One solution is to enclose every word in a span tag using script. Then if the Y dimension of a given span tag is less than that of it's immediate predecessor then a line break has occurred.


based on GuyPaddock's answer from above, this seems to work for me

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

the trick here is to increase the line-height so much that it will "swallow" any browser / OS differences in the way that they render fonts

Checked it with various stylings and different font sizes / families only thing that it doesn't take into account (since in my case it didnt matter), is the padding - which can easily be added to the solution.


You should be able to split('\n').length and get the line breaks.

update: this works on FF/Chrome but not 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>

I wasnt satisfied with the answers here and on other questions. The highest rated answer doesn't take padding or border into account, and therefore obviously ignores box-sizing as well. My answer combines some techniques here and and on other threads to get a solution that works to my satisfaction.

It isnt perfect: When no numerical value was able to be retrieved for the line-height (e.g. normal or inherit), it just uses the font-size multiplied by 1.2. Perhaps someone else can suggest a reliable way to detect the pixel value in those cases.

Other than that, it has been able to correctly handle most of the styles and cases I have thrown at it.

jsFiddle for playing around and testing. Also inline below.

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


No, not reliably. There are simply too many unknown variables

  1. What OS (different DPIs, font variations, etc...)?
  2. Do they have their font-size scaled up because they are practically blind?
  3. Heck, in webkit browsers, you can actually resize textboxes to your heart's desire.

The list goes on. Someday I hope there will be such a method of reliably accomplishing this with JavaScript, but until that day comes, your out of luck.

I hate these kinds of answers and I hope someone can prove me wrong.


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

You can see it in action here: https://jsfiddle.net/u0r6avnt/

Try resizing the panels on the page (to make the right side of the page wider or shorter) and then run it again to see that it can reliably tell how many lines there are.

This problem is harder than it looks, but most of the difficulty comes from two sources:

  1. Text rendering is too low-level in browsers to be directly queried from JavaScript. Even the CSS ::first-line pseudo-selector doesn't behave quite like other selectors do (you can't invert it, for example, to apply styling to all but the first line).

  2. Context plays a big part in how you calculate the number of lines. For example, if line-height was not explicitly set in the hierarchy of the target element, you might get "normal" back as a line height. In addition, the element might be using box-sizing: border-box and therefore be subject to padding.

My approach minimizes #2 by taking control of the line-height directly and factoring in the box sizing method, leading to a more deterministic result.


For those who use 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);
}

If the div's size is dependent on the content (which I assume to be the case from your description) then you can retrieve the div's height using:

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

And divide by the font line height:

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

Or to get the line height if it hasn't been explicitly set:

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

You will also need to take padding and inter-line spacing into account.

EDIT

Fully self-contained test, explicitly setting line-height:

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


Clone the container object and write 2 letters and calculate the height. This return the real height with all style applied, line height, etc. Now, calculate the height object / the size of a letter. In Jquery, the height excelude the padding, margin and border, it is great to calculate the real height of each line:

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

If you use a rare font with different height of each letter, this does not works. But works with all normal fonts, like Arial, mono, comics, Verdana, etc. Test with your font.

Example:

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

Result: 6 Lines

Works in all browser without rare functions out of standards.

Check: https://jsfiddle.net/gzceamtr/


Following @BobBrunius 2010 suggestion I created this with jQuery. No doubt it could be improved but it may help some.

_x000D_
_x000D_
$(document).ready(function() {_x000D_
_x000D_
  alert("Number of lines: " + getTextLinesNum($("#textbox")));_x000D_
_x000D_
});_x000D_
_x000D_
function getTextLinesNum($element) {_x000D_
_x000D_
  var originalHtml = $element.html();_x000D_
  var words = originalHtml.split(" ");_x000D_
  var linePositions = [];_x000D_
        _x000D_
  // Wrap words in spans_x000D_
  for (var i in words) {_x000D_
    words[i] = "<span>" + words[i] + "</span>";_x000D_
  }_x000D_
        _x000D_
  // Temporarily replace element content with spans. Layout should be identical._x000D_
  $element.html(words.join(" "));_x000D_
        _x000D_
  // Iterate through words and collect positions of text lines_x000D_
  $element.children("span").each(function () {_x000D_
    var lp = $(this).position().top;_x000D_
    if (linePositions.indexOf(lp) == -1) linePositions.push(lp);_x000D_
  });_x000D_
        _x000D_
  // Revert to original html content_x000D_
  $element.html(originalHtml);_x000D_
        _x000D_
  // Return number of text lines_x000D_
  return linePositions.length;_x000D_
_x000D_
}
_x000D_
#textbox {_x000D_
  width: 200px;_x000D_
  text-align: center;_x000D_
}
_x000D_
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>_x000D_
<div id="textbox">Lorem ipsum dolor sit amet, consectetuer adipiscing elit,_x000D_
  <br>sed diam nonummy</div>
_x000D_
_x000D_
_x000D_


You can compare element height and element height with 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
}

Check out the function getClientRects() which can be used to count the number of lines in an element. Here is an example of how to use it.

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

It returns a javascript DOM object. The amount of lines can be known by doing this:

var amount_of_lines = message_lines.length;

It can return the height of each line, and more. See the full array of things it can do by adding this to your script, then looking in your console log.

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

Though a few things to note is it only works if the containing element is inline, however you can surround the containing inline element with a block element to control the width like so:

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

Though I don't recommend hard coding the style like that. It's just for example purposes.


In certain cases, like a link spanning over multiple rows in non justified text, you can get the row count and every coordinate of each line, when you use this:

var rectCollection = object.getClientRects();

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

This works because each line would be different even so slightly. As long as they are, they are drawn as a different "rectangle" by the renderer.


I found a way to calc the line number when I develop a html editor. The primary method is that:

  1. In IE you can call getBoundingClientRects, it returns each line as a rectangle

  2. In webkit or new standard html engine, it returns each element or node's client rectangles, in this case you can compare each rectangles, I mean each there must be a rectangle is the largest, so you can ignore those rectangles that height is smaller(if there is a rectangle's top smaller than it and bottom larger than it, the condition is true.)

so let's see the test result:

enter image description here

The green rectangle is the largest rectangle in each row

The red rectangle is the selection boundary

The blue rectangle is the boundary from start to selection after expanding, we see it may larger than red rectangle, so we have to check each rectangle's bottom to limit it must smaller than red rectangle's bottom.

        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

Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to html

Embed ruby within URL : Middleman Blog Please help me convert this script to a simple image slider Generating a list of pages (not posts) without the index file Why there is this "clear" class before footer? Is it possible to change the content HTML5 alert messages? Getting all files in directory with ajax DevTools failed to load SourceMap: Could not load content for chrome-extension How to set width of mat-table column in angular? How to open a link in new tab using angular? ERROR Error: Uncaught (in promise), Cannot match any routes. URL Segment

Examples related to dom

How do you set the document title in React? How to find if element with specific id exists or not Cannot read property 'style' of undefined -- Uncaught Type Error adding text to an existing text element in javascript via DOM Violation Long running JavaScript task took xx ms How to get `DOM Element` in Angular 2? Angular2, what is the correct way to disable an anchor element? React.js: How to append a component on click? Detect click outside React component DOM element to corresponding vue.js component