5

Suppose that you have something like <div>foo bar</div> in a web page. This might get rendered as

foo bar

or

foo
bar

depending on whether the element gets word wrapped (which depends on its width and the value of various css properties such as white-space, word-break, overflow-wrap, etc).

Is there a way to determine how has the browser rendered the text in such an element with respect to word wrapping? i.e., I am looking for javascript code that would return something like ["foo", "bar"] (in the case wrapping did occur).

It seems that getClientRects returns separate boxes for each line (it appears to be working for inline elements only). But I am looking something that also gives information about the text.

safsaf32
  • 1,517
  • 2
  • 13
  • 18
  • Would probably help if you explained your use case. Not entirely clear what you are needing – charlietfl Jul 06 '20 at 05:06
  • HTML wrap word depends on width, spaces between characters makes browser think that it's a word – Abhishek Pandey Jul 06 '20 at 05:08
  • If a block level element contains plain text only, it's easy to [get DOMRectList](https://jsfiddle.net/jayhr4f5/). But I'm not aware how this list can be used to get the text from the boxes. – Teemu Jul 06 '20 at 07:33

3 Answers3

5

If a block level element contains plain text only, you can get the text of the auto-wrapped lines using DOMRect object of two ranges. Create a range (range) of the textnode inside the element to get the text of a line, and another range (hTracker) to keep book of the height of DOMRect object, when increasing the end position of the ranges. When the height of the range changes, store the text in the previous range to an array, and set the start position of range to the end of the current line. The code is as the following:

function showLines(e) {
  const el = e.target,
    node = el.firstChild,
    range = document.createRange(),
    len = e.target.textContent.length,
    texts = [];
  let hTracker, y, oldY;
  range.selectNodeContents(el);
  range.collapse();
  hTracker = range.cloneRange();
  hTracker.setEnd(node, 1);
  oldY = hTracker.getBoundingClientRect().height;
  for (let n = 0; n < len; n++) {
    hTracker.setEnd(node, n);
    range.setEnd(node, n);
    y = hTracker.getBoundingClientRect().height;
    if (y > oldY || n === len - 1) {
      // Line changed, resume the previous range (not when at the end of the text)
      range.setEnd(node, n - (n !== len - 1));
      // Store the text of the line
      texts.push(range.toString());
      // Set the start of the range to the end of the previous line
      range.setStart(node, n - 1);
      oldY = y;
    }
  }
  console.log(texts);
};

document.addEventListener('click', showLines);
<div>
  depending on whether the element gets word wrapped (which depends on its width and the value of various css properties such as white-space, word-break, overflow-wrap, etc). Is there a way to determine how has the browser rendered the text in such an element
  with respect to word wrapping? i.e., I am looking for javascript code that would return something like ["foo", "bar"] (in the case wrapping did occur). It seems that getClientRects returns separate boxes for each line (it appears to be working for inline
  elements only). But I am looking something that also gives information about the text.
</div>

Range.getBoundingClientRect is not a standard method yet, but it's widely supported. This could probably be done with a single range too, but this way the idea might be easier to understand.

A demo at jsFiddle to play with.

Teemu
  • 22,918
  • 7
  • 53
  • 106
2

I'm not sure if there is a way to do this with DOM, but this is my solution:

let divs = document.querySelectorAll('div');

for (let div of divs) {
    let wordBreak = [];
    const words = div.innerText.split(" ");
    div.innerText = words[0];
    let beforeBreak = words[0];
    let height = div.offsetHeight;

    for (let i = 1; i < words.length; i++) {
        div.innerText += ' ' + words[i];
        if (div.offsetHeight > height) {
            height = div.offsetHeight;
            wordBreak.push(beforeBreak);
            beforeBreak = words[i];
        } else {
            beforeBreak += ' ' + words[i];
        }
    }
    
    wordBreak.push(beforeBreak);
    console.log(wordBreak);
}
<div style="width: 5%; overflow-wrap: break-word">foo bar</div>
<div style="overflow-wrap: break-word;">foo bar</div>
<div style="width: 50%; overflow-wrap: break-word">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin sit amet tellus et elit ultricies iaculis bibendum
    eu arcu. Nam ut ante tellus, sed semper velit. Pellentesque ac odio ligula. Donec sem dui, iaculis vel euismod a,
    vulputate ac mi. Nam ut lorem neque. Praesent lacinia molestie est, et luctus nunc venenatis id. Vivamus id mi elit.
    Fusce in magna at diam commodo tincidunt at quis ante. Donec ac dolor risus. Quisque vehicula ultrices faucibus
</div>

Note: this answer is similar to this one but using Native JS and the end result as you described.

0

I am looking for javascript code that would return something like ["foo", "bar"] (in the case wrapping did occur).

I don't have same solution but i have an idea which is very similar to this. see below snippet and inspect the code and then reduce the screen size and agin inspect the code. you will under stand

It will help you to get the lines acc to device width. { span class="line-0", span class="line-1" }

var words = $('#example').text().replace(/\t/g, ' ').replace(/\r/g, ' ').replace(/\n/g, ' ').replace(/\s+/g, ' ').split(' ');
//console.log(words);
$('#example').html('<span>' + words.join('</span> <span>') + '</span>');
var lines = [];
$('#example span').each(

  function(i, e) {
    var offsettop = $(e).offset();
    if (lines.indexOf(offsettop.top) === -1) {
      lines.push(offsettop.top);

    }
    $(e).addClass('line' + lines.indexOf(offsettop.top));
  });

var line = [];
$('#example span.line1').css('background', 'red').each(function(i, e) {
  line.push($(e).html());
});
#example {
  l1ine-height: 200%;
  font-size: 2em;
  text-transform: uppercase;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p id=example>Foo Bar</p>
Anmol Juneja
  • 325
  • 1
  • 9