Aurore
Aurore

Reputation: 746

Split multiline text into specific-width lines

I've been trying to have multiple paragraphs with different width but each time a line breaks, the line after goes in a new paragraph <p></p>. I need four of them that I can later style independently. Here is a screenshot of what I'm trying to achieve, as an exemple: enter image description here

here is the original question where I found the original script.

What I would like to do:

I've managed to adapt it and understand it quite a bit, to make it work when the lines are all the same. Basically :

 <p style="width:700px">I am some ra</p>
 <p style="width:700px">ndom text. I</p>
 <p style="width:700px">am Some text</p>
 <p style="width:700px">.blabla</p>

But now I'm facing an issue:

I'd like to specify different width for the individual lines in my css so the script breaks them perfectly. In other words: A line of text takes all the width of the div paragraph and the remaining text moves into another paragraph (for exemple #text2) until we reach four div paragraphs.

Something like that:

<p style="width:50px">I am some ra</p>
<p style="width:900px">ndom text. I</p>
<p style="width:800px">am Some text</p>
<p style="width:1000px">.blabla</p>

EDIT: There is some progress so far. The text seems to breaks related to the width of the paragraph just before.

Here is the project:

    var p = document.getElementById("p");
    var menu = document.getElementsByClassName("menu");
    var width = p.clientWidth;
    var parent = p.parentNode;
    var line = document.createElement("span");
    line.style.display = "inline-block";
    line.style.visibility = "hidden";
    var content = p.innerHTML;
    document.body.appendChild(line);
    var start = 0;
    var i = 1;
    let run = 1;
    while (i < content.length) {
      line.innerHTML = content.substring(start, i);
      console.log(i + " " + content.length);
      console.log("#text" + run + " width: " + menu[run].clientWidth);
      console.log("run: " + run);
      if (line.clientWidth > menu[run + 1].clientWidth) {
        var newp = document.createElement("p");
        newp.id = "text" + run;
        newp.className = "menu";
        newp.innerHTML = content.substring(start, i - 1);
        parent.insertBefore(newp, p);
        start = i - 1;
        i = start + 1;
        run++;
      } else {
        i++;
      }
    }
    newp = document.createElement("p");
    newp.id = "textbis" + run;
    newp.className = "menu";
    newp.innerHTML = content.substring(start);
    parent.insertBefore(newp, p);
    parent.removeChild(p);
div {
  word-break: break-all;
  background-color: lightblue;
  width: 700px;
}
p {
  background-color: lightgreen;
}
#text0 {
  width: 50px;
}

#text1 {
  width: 50px;
}

#text2 {
  width: 200px;
}

#text3 {
  width: 500px;
}

#text4 {
  width: 700px;
}
    <div>
      <p class="menu" id="text1"></p>
      <p class="menu" id="text2"></p>
      <p class="menu" id="text2"></p>
      <p class="menu" id="p">
        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Saepe,
        aliquam? Lorem ipsum dolor sit amet consectetur adipisicing elit.
        Quibusdam quaerat iste earum quos eligendi atque aliquam veniam facilis
        quis ducimus. Lorem ipsum dolor sit amet consectetur adipisicing elit.
        Iure, quisquam! Temporibus consequuntur laboriosam quos odio maxime
        iusto at dolore quod ipsa eaque voluptas mollitia, vel odit inventore
        sapiente aut!
      </p>
    </div>

Upvotes: 4

Views: 617

Answers (1)

akinuri
akinuri

Reputation: 12029

It might not be the best choice performance-wise, but here's one way to do it.

let textarea = document.querySelector("textarea");
let output   = document.querySelector("textarea + div");

let widths = [100, 450, 400, 500, 9999];

textarea.addEventListener("input", () => {
    output.innerHTML = null;
    console.time("split took");
    let lines = splitTextByLineWidths(textarea.value, widths);
    console.timeEnd("split took");
    lines.forEach((line, i) => {
        let p = document.createElement("p");
        if (widths[i] < window.innerWidth) {
            p.style.width = widths[i] + "px";
        }
        p.textContent = line;
        output.append(p);
    });
});
textarea.dispatchEvent(new Event("input"));

function splitTextByLineWidths(text, lineWidths) {
    let lines = [];
    let renderer = document.createElement("div");
    renderer.style.position = "absolute";
    renderer.style.left = window.innerWidth * -3 + "px";
    renderer.style.top = window.innerHeight * -3 + "px";
    document.body.appendChild(renderer);
    lineWidths.forEach(lineWidth => {
        let measure = document.createElement("div");
        measure.style.display = "table";
        measure.textContent = "dummy";
        renderer.appendChild(measure);
        let lineHeight = measure.offsetHeight;
        let activeText = text;
        measure.textContent = activeText;
        measure.style.width = lineWidth + "px";
        let height = measure.offsetHeight;
        while (height > lineHeight) {
            activeText = activeText.slice(0, -1);
            measure.textContent = activeText;
            height = measure.offsetHeight;
        }
        lines.push(activeText);
        text = text.slice(activeText.length);
    });
    renderer.remove();
    return lines;
}
textarea + div {
    background-color: lightblue;
    padding: 1em;
}
textarea + div p {
    background-color: peachpuff;
}
<textarea cols="100" rows="6">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque varius nisi purus, in dignissim justo egestas et. Suspendisse suscipit, metus vitae facilisis cursus, diam est malesuada tellus, eu viverra risus sapien nec neque.</textarea>
<div></div>

This method can be further improved by using smarter ways to trim the text. Currently, it's triming a single character and checking if the text overflows the first line. This is an expensive operation.

One can devise a way to increase the trim length, and instead of going one way (trim only), can go both ways (trim and grow/put back) when the text underflows the width.

Upvotes: 2

Related Questions