Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find rendered line breaks with javascript

Tags:

javascript

I have this situation :

div { width: 200px }
<div> example example example example example</div>

Text jumps to next line automatically when filling the full width of the <div>.

Using javascript how can I have the rendered content in the above line?

note: In the character string, there is no newline character

expected result from above snippet:

"example example example" corresponding with row 1 and "example example" corresponding with row 2

like image 663
Tuan Jihoo Avatar asked Apr 10 '19 04:04

Tuan Jihoo


People also ask

How do you use Nextline in JavaScript?

The newline character is \n in JavaScript and many other languages. All you need to do is add \n character whenever you require a line break to add a new line to a string.

How break JavaScript code into multiple lines?

There are two ways to break JavaScript code into several lines: We can use the newline escape character i.e “\n”. if we are working on the js file or not rendering the code to the html page. We can use the <br> tag.

How do you breakline?

To add spacing between lines or paragraphs of text in a cell, use a keyboard shortcut to add a new line. Click the location where you want to break the line. Press ALT+ENTER to insert the line break.

How do you put a line break inside a string in HTML?

<br>: The Line Break element. The <br> HTML element produces a line break in text (carriage-return).


1 Answers

You can make use of the Range API and its handy getBoundingClientRect() method to determine which character marks the seizure in a TextNode.

Note that this obviously needs to be recalculated every time the window is resized / something changes the layout.

function getLineBreaks(node) {
  // we only deal with TextNodes
  if(!node || !node.parentNode || node.nodeType !== 3)
    return [];
  // our Range object form which we'll get the characters positions
  const range = document.createRange();
  // here we'll store all our lines
  const lines = [];
  // begin at the first char
  range.setStart(node, 0);
  // initial position
  let prevBottom = range.getBoundingClientRect().bottom;
  let str = node.textContent;
  let current = 1; // we already got index 0
  let lastFound = 0;
  let bottom = 0;
  // iterate over all characters
  while(current <= str.length) {
    // move our cursor
    range.setStart(node, current);
    if(current < str.length -1)
     range.setEnd(node, current+1);
    bottom = range.getBoundingClientRect().bottom;
    if(bottom > prevBottom) { // line break
      lines.push(
        str.substr(lastFound , current - lastFound) // text content
      );
      prevBottom = bottom;
      lastFound = current;
    }
    current++;
  }
  // push the last line
  lines.push(str.substr(lastFound));

  return lines;
}

console.log(getLineBreaks(document.querySelector('.test').childNodes[0]));
div.test {
  width: 50px;
  margin-bottom: 100px;
  word-break: break-all;
}

body>.as-console-wrapper{max-height:100px}
<div class="test">This is some quite long content that will wrap in multiple lines</div>

And if you need the relative y position of each lines:

function getLineBreaks(node) {
  // we only deal with TextNodes
  if(!node || !node.parentNode || node.nodeType !== 3)
    return [];
  // our Range object form which we'll get the characters positions
  const range = document.createRange();
  // here we'll store all our lines
  const lines = [];
  // begin at the first character
  range.setStart(node, 0);
  // get the position of the parent node so we can have relative positions later
  let contTop = node.parentNode.getBoundingClientRect().top;
  // initial position
  let prevBottom = range.getBoundingClientRect().bottom;
  let str = node.textContent;
  let current = 1; // we already got index 0
  let lastFound = 0;
  let bottom = 0;
  // iterate over all characters
  while(current <= str.length) {
    // move our cursor
    range.setStart(node, current);
    if(current < str.length - 1)
      range.setEnd(node, current+1); // wrap it (for Chrome...)
    bottom = range.getBoundingClientRect().bottom;
    if(bottom > prevBottom) { // line break
      lines.push({
        y: prevBottom - (contTop || 0), // relative bottom
        text: str.substr(lastFound , current - lastFound) // text content
      });
      prevBottom = bottom;
      lastFound = current;
    }
    current++;
  }
  // push the last line
  lines.push({
    y: bottom - (contTop || 0),
    text: str.substr(lastFound)
  });

  return lines;
}

console.log(getLineBreaks(document.querySelector('.test').childNodes[0]));
div.test {
  width: 50px;
  margin-bottom: 100px;
}

body>.as-console-wrapper{max-height:100px}
<div class="test">This is some quite long content that will wrap in multiple lines</div>

For the ones who need it to work over elements instead of a single text-node, here is a rewrite, which may very well fail (e.g with RTL direction) but which should be fine for most cases.

function getLineBreaks(elem) {
  // our Range object form which we'll get the characters positions
  const range = document.createRange();
  // here we'll store all our lines
  const lines = [];
  const nodes = grabTextNodes(elem);
  let left = 0;
  // get the position of the parent node so we can have relative positions later
  let contTop = nodes[0].parentNode.getBoundingClientRect().top;
  // initial position
  let prevLeft = null;
  let lineText = "";
  let startRange = null;
  for (const node of nodes) {
    let nodeText = node.textContent;
    const textLength = nodeText.length;
    let rangeIndex = 0;
    let textIndex = 0;
    while (rangeIndex <= textLength) {
      range.setStart(node, rangeIndex);
      if (rangeIndex < textLength - 1) {
        range.setEnd(node, rangeIndex + 1); // wrap the range (for Chrome...)
      }
      left = range.getBoundingClientRect().right;
      if (prevLeft === null) { // first pass
        prevLeft = left;
        startRange = range.cloneRange();
      } else if (left < prevLeft) { // line break
        // store the current line content
        lineText += nodeText.slice(0, textIndex);
        startRange.setEnd(range.endContainer, range.endOffset);
        const {
          bottom
        } = startRange.getBoundingClientRect();
        lines.push({
          y: bottom - contTop,
          text: lineText
        });
        // start a new line
        prevLeft = left;
        lineText = "";
        nodeText = nodeText.slice(textIndex);
        textIndex = 0;
        startRange = range.cloneRange();
      }
      rangeIndex++;
      textIndex++;
      prevLeft = left;
    }
    // add the remaining text from this node into the current line content
    lineText += nodeText;
  }
  // push the last line
  startRange.setEnd(range.endContainer, range.endOffset);
  const { bottom } = startRange.getBoundingClientRect();
  lines.push({
    y: bottom - contTop,
    text: lineText
  });
  return lines;
}

console.log(getLineBreaks(document.querySelector('.test')));

function grabTextNodes(elem) {
  const walker = document.createTreeWalker(elem, NodeFilter.SHOW_TEXT, null);
  const nodes = [];
  while (walker.nextNode()) {
    nodes.push(walker.currentNode);
  }
  return nodes;
}
div.test {
  width: 150px;
  margin-bottom: 100px;
}

.red {
  color: red;
}
<div class="test"><span class="red">This</span> is some quite long content that will wrap in <span class="red">mutiple</span> lines..</div>
like image 195
Kaiido Avatar answered Oct 31 '22 22:10

Kaiido