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

Viewed 156380

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?

20 Answers

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:

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>

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.

I am convinced that it is impossible now. It was, though.

IE7’s implementation of getClientRects did exactly what I want. Open this page in IE8, try refreshing it varying window width, and see how number of lines in the first element changes accordingly. Here’s the key lines of the javascript from that page:

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

Unfortunately for me, Firefox always returns one client rectangle per element, and IE8 does the same now. (Martin Honnen’s page works today because IE renders it in IE compat view; press F12 in IE8 to play with different modes.)

This is sad. It looks like once again Firefox’s literal but worthless implementation of the spec won over Microsoft’s useful one. Or do I miss a situation where new getClientRects may help a developer?

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.

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>

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.

My solution(may be duplicated): make this element nowrap to calculate lineHeight, then calculate lineNumber by oneLineHeight / lineHeight

function getLineInfo(root: HTMLElement): {
  lineNumber: number;
  lineHeight: number;
  elHeight: number;
} {
  const oldOverFlow = root.style.overflow;
  const oldWhiteSpace = root.style.whiteSpace;

  root.style.overflow = "hidden";
  root.style.whiteSpace = "nowrap";

  const lineHeight = root.offsetHeight;
  root.style.overflow = oldOverFlow;
  root.style.whiteSpace = oldWhiteSpace;

  const lineNumber = Math.round(root.offsetHeight / lineHeight);

  return {
    lineNumber: lineNumber,
    lineHeight,
    elHeight: root.offsetHeight,
  };
}

You could count the number of line breaks in the element's innerText, like this:

const text = anyDivElement.innerText;
const lines = text.split(/\r\n|\r|\n/).length;

Easiest way to do this is calculating line height and divide it by element height. This code works for any Kind of elements:

function getStyle(el,styleProp)
{
    var x = el;
    if (x.currentStyle)
        var y = x.currentStyle[styleProp];
    else if (window.getComputedStyle)
        var y = document.defaultView.getComputedStyle(x,null).getPropertyValue(styleProp);
    return y;
}
function calculateLineHeight (element) {

  var lineHeight = parseInt(getStyle(element, 'line-height'), 10);
  var clone;
  var singleLineHeight;
  var doubleLineHeight;

  if (isNaN(lineHeight)) {
    clone = element.cloneNode();
    clone.innerHTML = '<br>';
    element.appendChild(clone);
    singleLineHeight = clone.offsetHeight;
    clone.innerHTML = '<br><br>';
    doubleLineHeight = clone.offsetHeight;
    element.removeChild(clone);
    lineHeight = doubleLineHeight - singleLineHeight;
  }

  return lineHeight;
}

function getNumlines(el){return Math.ceil(el.offsetHeight / calculateLineHeight (el))}



console.log(getNumlines(document.getElementById('g1')))
.Text{font-size: 28px;}
@media screen and (max-width: 780px) {
            .Text{font-size: 50px;}
        }
<div><span class="Text" id="g1" >
                This code works for any Kind of elements: bla blo bli bla blo bli bla blo bli bla blo bli bla blo bli bla blo bli bla blo bli bla blo bli bla blo bli bla blo bli bla blo bli bla blo bli bla blo bli bla blo bli bla blo bli bla blo bli bla blo bli bla blo bli bla blo bli </span>
        </div>

Another simple solution, by using getClientRects(), not well tested:

export function getLineInfo(root: HTMLElement): {
  lineNumber: number;
  lineHeight: number;
  elHeight: number;
} {
  const test = document.createElement("span");
  test.textContent = " ";

  root.appendChild(test);
  const first = test.getClientRects();
  root.insertBefore(test, root.firstChild);
  const second = test.getClientRects();

  const lineHeight = first[0].y - second[0].y;

  test.remove();

  const lastPadding = lineHeight - first[0].height;
  const offsetHeight = first[0].bottom - second[0].top + lastPadding;

  const lineNumber = Math.round(offsetHeight / lineHeight);

  return {
    lineNumber: lineNumber,
    lineHeight,
    elHeight: offsetHeight,
  };
}
Related