How can I count text lines inside an DOM element?

2019-01-01 08:54发布

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?

16条回答
不流泪的眼
2楼-- · 2019-01-01 09:06

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
查看更多
情到深处是孤独
3楼-- · 2019-01-01 09:10

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.

查看更多
公子世无双
4楼-- · 2019-01-01 09:10

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.

查看更多
伤终究还是伤i
5楼-- · 2019-01-01 09:10

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>
查看更多
与风俱净
6楼-- · 2019-01-01 09:14

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.

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>

查看更多
像晚风撩人
7楼-- · 2019-01-01 09:16

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?

查看更多
登录 后发表回答