Как я могу подсчитать текстовые строки внутри элемента DOM? Могу я?

127

Мне интересно, есть ли способ, например, подсчитать строки внутри div. Скажем, у нас есть такой div:

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

В зависимости от многих факторов, div может содержать одну, две или даже четыре строки текста. Есть ли способ узнать об этом сценарий?

Другими словами, представлены ли вообще автоматические разрывы в DOM?

Бути-окса
источник

Ответы:

89

Если размер div зависит от содержимого (что, как я предполагаю, соответствует вашему описанию), вы можете получить высоту div, используя:

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

И разделите на высоту строки шрифта:

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

Или чтобы получить высоту строки, если она не была явно установлена:

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

Вам также необходимо принять во внимание отступы и межстрочный интервал.

РЕДАКТИРОВАТЬ

Полностью автономный тест, явно устанавливающий высоту строки:

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>

Бассейн
источник
7
Это хорошее начало, за исключением того, что высота строки может быть не всегда установлена. Вы должны получить это из вычисленного стиля элемента.
Chetan S
Если подумать, высота строки не всегда должна быть числовой (или в пикселях). Поэтому, если ваш код полагается на это и ваше соглашение CSS это позволяет, вам, вероятно, следует установить высоту строки в пикселях.
Chetan S
6
@Chetan - установка размеров текста в пикселях обычно считается плохой вещью. astahost.com/Sizes-Webdesign-Em-Vs-Px-t8926.html
annakata
6
Это может работать только в простейшем случае (например, в моем примере). Если внутри есть промежутки, встроенные блочные элементы и т. Д., Прямое деление по (родительскому) размеру шрифта бесполезно. Тем не менее, это лучше, чем ничего, спасибо.
buti-oxa
2
Я думаю, что лучший метод получения вычислений line-height(который явно не установлен) - это использоватьwindow.getComputedStyle(element, null).getPropertyValue('line-height')
rnevius
20

Одно из решений - заключить каждое слово в тег span с помощью скрипта. Затем, если размер Y данного тега span меньше, чем у его непосредственного предшественника, произошел разрыв строки.

Боб Бруниус
источник
Умная! Как ты это делаешь? Я предполагаю, что вы предполагаете только текстовый абзац (как я в примере). Я не имел в виду это ограничение, но это может быть нормально, при условии, что скрипт не выйдет из строя, когда в абзаце есть что-то еще.
buti-oxa
1
Если подумать, этот метод не работает, если в линии есть вертикально выровненные промежутки. Таким образом, даже абзац, содержащий только текст, может быть засчитан неправильно.
buti-oxa,
В моем div есть текст и изображения ... к счастью, изображения абсолютно позиционированы, поэтому я думаю, что они будут исключены. : D В любом случае я использовал тот же код, который вы предложили, но с div, а не с диапазоном, спасибо, хотя +1!
Альберт Реншоу
20

Обратите внимание на функцию getClientRects (), которую можно использовать для подсчета количества строк в элементе. Вот пример того, как его использовать.

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

Он возвращает объект DOM javascript. Количество строк можно узнать следующим образом:

var amount_of_lines = message_lines.length;

Он может возвращать высоту каждой строки и многое другое. Посмотрите полный набор вещей, которые он может делать, добавив это в свой скрипт, а затем заглянув в журнал консоли.

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

Хотя следует отметить несколько моментов, это работает, только если содержащий элемент является встроенным, однако вы можете окружить содержащий встроенный элемент блочным элементом для управления шириной следующим образом:

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

Хотя я не рекомендую жестко кодировать такой стиль. Это просто для примера.

Digital-Кристи
источник
3
Этот и другой ответ на основе getClientRects намного лучше принятого
matteo
2
Это должен быть принятый ответ. Спасибо @ Digital-Кристи
Антуан
12

Меня не устроили ответы здесь и на другие вопросы. Самый высокий рейтинг ответ не принимает paddingили borderво внимание, и поэтому очевидно , игнорируетbox-sizing , а также. Мой ответ сочетает в себе некоторые методы здесь и в других темах, чтобы получить решение, которое удовлетворит меня.

Это не идеально: когда не удалось получить числовое значение для line-height(например, normalили inherit), оно просто использует font-sizeумноженное на 1.2. Возможно, кто-то другой может предложить надежный способ определения значения пикселя в таких случаях.

Помимо этого, он смог правильно обрабатывать большинство стилей и случаев, которые я ему использовал.

jsFiddle для игры и тестирования. Также в строке ниже.

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>

Джефф
источник
полезный пост ... Спасибо
Sinto
Хорошее рассмотрение, но в моем случае высота одной строки div больше, чем высота строки на 1 пиксель, без каких-либо отступов и границ. Таким образом, получение высоты строки путем расчесывания вашего ответа и ответа @ e-info128 (метод cloneNode) может быть лучшим выбором,
Эрик,
8

Клонируйте объект-контейнер и напишите 2 буквы и вычислите высоту. Это возвращает реальную высоту со всеми примененными стилями, высотой строки и т. Д. Теперь рассчитайте высоту объекта / размер буквы. В JQuery высота превышает отступы, поля и границу, здорово вычислить реальную высоту каждой строки:

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

Если вы используете редкий шрифт с разной высотой каждой буквы, это не сработает. Но работает со всеми обычными шрифтами, такими как Arial, mono, comics, Verdana и т. Д. Проверьте свой шрифт.

Пример:

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

Результат: 6 Lines

Работает во всех браузерах без редких нестандартных функций.

Проверить: https://jsfiddle.net/gzceamtr/

е-info128
источник
Прежде всего, хочу сказать большое спасибо, именно то, что я искал. Не могли бы вы объяснить каждую строку функции вычисления. Заранее большое спасибо. SYA :)
LebCit
1
для тех, кому нужен не-jQuery ответ: clonecloneNode, hidestyle.visibility = "hidden", html('a<br>b')textContent='a\r\nb', appendTo('body')document.documentElement.appendChild, height()getBoundingClientRect().height
Эрик
Поскольку я получаю ошибку в 1 пиксель между высотой строки и высотой div, я рекомендую этот ответ. Сочетание этого и ответа @Jeff будет еще лучше
Эрик
6

Для тех, кто использует 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);
}
Пеньковский
источник
jsfiddle.net/EppA2/9 менее точен, но согласно этому может лучше поддерживаться различными браузерами
penkovsky
8
Когда jQuery возвращает "normal" из $(selector).css('line-height');, вы не получите число из этой функции.
bafromca
5

Я убежден, что сейчас это невозможно. Хотя это было.

Реализация getClientRects в IE7 сделала именно то, что я хочу. Откройте эту страницу в IE8, попробуйте обновить ее, меняя ширину окна, и посмотрите, как соответственно изменится количество строк в первом элементе. Вот ключевые строки javascript с этой страницы:

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

К сожалению, для меня Firefox всегда возвращает один клиентский прямоугольник на элемент, и IE8 теперь делает то же самое. (Страница Мартина Хоннена работает сегодня, потому что IE отображает ее в представлении совместимости IE; нажмите F12 в IE8, чтобы поиграть в разных режимах.)

Это печально. Похоже, что буквальная, но бесполезная реализация спецификации Firefox в очередной раз победила полезную реализацию Microsoft. Или мне не хватает ситуации, когда новый getClientRects может помочь разработчику?

Бути-окса
источник
2
Как указано в документации Mozilla element.getClientRects , по крайней мере, для встроенных элементов спецификация W3C действительно приводит к прямоугольнику для каждой строки текста - не идеально, но по крайней мере что-то.
natevw
getClientRects (). length - правильный путь. getComputedStyle () может возвращать такие значения, как «наследование», «нормальный» и «авто».
Биньямин
4

на основе ответа GuyPaddock сверху, это, похоже, работает для меня

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

Хитрость здесь в том, чтобы увеличить высоту строки настолько, чтобы она «поглотила» любые различия браузера / ОС в способе отображения шрифтов.

Проверил его с помощью различных стилей и разных размеров / семейств шрифтов. Единственное, что он не принимает во внимание (поскольку в моем случае это не имело значения), это отступы, которые можно легко добавить в решение.

Маор Йосеф
источник
2

Нет, не достоверно. Просто слишком много неизвестных переменных

  1. Какая ОС (разные DPI, варианты шрифтов и т. Д.)?
  2. Увеличили ли они размер шрифта, потому что они практически слепые?
  3. Черт возьми, в браузерах webkit вы можете изменять размер текстовых полей по своему желанию.

Этот список можно продолжить. Я надеюсь, что когда-нибудь появится такой метод надежного решения этой задачи с помощью JavaScript, но пока не наступит этот день, вам не повезло.

Я ненавижу такие ответы и надеюсь, что кто-нибудь сможет доказать, что я неправ.

KyleFarris
источник
3
Если посчитать строки после рендеринга, все эти неизвестные значения не имеют. Ваша точка зрения, конечно, применима, если вы попытаетесь «угадать», сколько строк использует браузер при рендеринге текста.
Саймон
Я бы сказал, что переменную увеличения и / или изменения размера текста пользователями можно игнорировать. Я никогда не видел сайта, дизайн которого был бы оптимизирован для масштабирования окна (после загрузки страницы) или изменения размера текста в браузере, поэтому я не думаю, что это важные факторы в этом конкретном вопросе. Однако я согласен, что нет надежных решений, только «предположения», основанные на пикселях, как указывали другие люди.
Kalko 05
2

Вы должны иметь возможность разделить ('\ n'). Length и получить разрывы строк.

обновление: это работает в FF / Chrome, но не в 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>
Чад Грант
источник
1
Попробуйте и дайте нам знать, как это происходит. Я очень серьезен, но не саркастичен. Вы можете использовать этот код в консоли FireBug на этой самой странице. var the_text = $ ('. welovestackoverflow p'). текст (); var numlines = the_text.split ("\ n"). length; оповещения (numlines);
KyleFarris
3
Спасибо за этот сюрприз, я не знал, что это возможно, но я не этого хочу. Кажется, что Jquery подсчитывает жесткие разрывы строк в источнике, и меня интересуют автоматические разрывы строк в результате. В случае вашего div "One Two Three" результат должен быть 1, потому что браузер поместил весь текст в одну строку.
buti-oxa
Тогда я неправильно понял ваш вопрос. Тогда вы хотите найти вычисленную высоту строки.
Чад Грант,
1

getClientRectsвернуть клиентские прямоугольники, подобные этому, и если вы хотите получить строки, используйте следующую функцию, подобную этой

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
источник
1

Я нашел способ вычислить номер строки при разработке редактора html. Основной метод заключается в следующем:

  1. В IE вы можете вызвать getBoundingClientRects, он возвращает каждую строку в виде прямоугольника

  2. В webkit или новом стандартном движке html он возвращает каждый элемент или клиентские прямоугольники узла, в этом случае вы можете сравнить каждый прямоугольник, я имею в виду, что каждый прямоугольник должен быть самым большим, поэтому вы можете игнорировать те прямоугольники, высота которых меньше (если есть верхняя часть прямоугольника меньше его, а нижняя больше его, условие верно.)

так что посмотрим результат теста:

введите описание изображения здесь

Зеленый прямоугольник - это самый большой прямоугольник в каждой строке.

Красный прямоугольник - граница выделения.

Синий прямоугольник - это граница от начала до выделения после расширения, мы видим, что он может быть больше красного прямоугольника, поэтому мы должны проверять нижнюю часть каждого прямоугольника, чтобы ограничить ее размер, меньший, чем нижняя часть красного прямоугольника.

        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
источник
1

Попробуйте это решение:

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

Вы можете увидеть это в действии здесь: https://jsfiddle.net/u0r6avnt/

Попробуйте изменить размер панелей на странице (чтобы сделать правую часть страницы шире или короче), а затем запустите его еще раз, чтобы убедиться, что он может достоверно определить количество строк.

Эта проблема сложнее, чем кажется, но большая часть трудностей возникает из двух источников:

  1. Рендеринг текста в браузерах слишком низкоуровневый, чтобы его можно было напрямую запрашивать из JavaScript. Даже псевдоселектор CSS :: first-line ведет себя не так, как другие селекторы (вы не можете инвертировать его, например, чтобы применить стиль ко всем, кроме первой строки).

  2. Контекст играет большую роль в том, как вы рассчитываете количество строк. Например, если высота строки не была явно установлена ​​в иерархии целевого элемента, вы можете получить "нормальный" обратно как высоту строки. Кроме того, элемент может использоваться box-sizing: border-boxи, следовательно, подлежать заполнению.

Мой подход сводит к минимуму № 2, беря под контроль высоту строки напрямую и принимая во внимание метод определения размера блока, что приводит к более детерминированному результату.

GuyPaddock
источник
1

В определенных случаях, например, если ссылка занимает несколько строк в не выровненном тексте, вы можете получить количество строк и каждую координату каждой строки, используя это:

var rectCollection = object.getClientRects();

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

Это работает, потому что каждая строка даже немного отличается. Пока они есть, они отображаются средством визуализации как другой «прямоугольник».

Фирш - LetsWP.io
источник
0

Следуя предложению @BobBrunius 2010, я создал это с помощью jQuery. Несомненно, это можно улучшить, но некоторым это может помочь.

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

йозеф
источник
0

Вы можете сравнить высоту элемента и высоту элемента с помощью 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é
источник