4

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>
Aurore
  • 696
  • 4
  • 16
  • There's an error in the code snippet. It doesn't work. You might want to fix that. – akinuri Jul 29 '21 at 15:07
  • 1
    Seems to be in an infinite loop or something; logs show an ever-increasing `i` with a `content.length` of a constant 437. The `nombre` goes 0 1 2 and then back to 0. I stopped when `i` hit 580531. – Heretic Monkey Jul 29 '21 at 15:44
  • As @HereticMonkey mentioned, now you've got another :) And as a side note, I did not pay much attention to the question itself because it seems a little ambiguous, and could use some improvement. That is why I wanted to see the code in action. Thought it could paint a better picture than the description. – akinuri Jul 29 '21 at 16:45
  • edited the code so it doesn't loop or do anything weird. it's probably more clear now! Thanks – Aurore Jul 29 '21 at 16:53
  • Maybe I'm missing something: this is _your_ html, just add your paragraph markup around your sentences instead of putting them all in a single div, then use CSS like you would on any other occasion to style each and all paragraphs as you need? – Mike 'Pomax' Kamermans Jul 29 '21 at 16:55
  • I'm guessing if you were to explain the end result you're after, there may be other ways to achieve the same effect without JavaScript. For instance, [CSS triangle containing text](https://stackoverflow.com/q/15112819/215552) shows how to achieve irregular line widths without needing separate HTML elements surrounding each line. – Heretic Monkey Jul 29 '21 at 16:55
  • @Mike'Pomax'Kamermans i'd like it to be dynamic. So no breakpoints. – Aurore Jul 29 '21 at 16:57
  • 1
    Why not? That's _literally_ what HTML is supposed to be for: marked up data. If this isn't additional content that is loaded in after the main page has already loaded, not putting paragraph elements around those sentences and then trying to do it after the fact gets pretty close to "doing html wrong". – Mike 'Pomax' Kamermans Jul 29 '21 at 16:59
  • @HereticMonkey edited the post, so it's more clear maybe? – Aurore Jul 29 '21 at 17:07
  • Well, as the answerer says at the end of [the answer from which you got the code](https://stackoverflow.com/a/24984694/215552): "I’m pretty sure you should have a completely different approach, but I cannot help with the ultimate problem you are trying to solve, as it was not described." – Heretic Monkey Jul 29 '21 at 17:12

1 Answers1

2

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.

akinuri
  • 10,690
  • 10
  • 65
  • 102
  • thanks, indeed, that's a way to work on it. I've also edited my code snippet with my progress so far. It's pretty weird and I must be missing something since the first `p` seems to be ignored... – Aurore Jul 29 '21 at 19:02
  • @pual I think you should isolate your problem from the implementation details. You have a long/multiline text, and you want it split into individual lines that vary in length. This can be solved in a single function. Your snippet is convoluted. From where the text and the widths come should not matter. These are implementation details. If they do matter, you should give more details about the page, and explain what exactly you're trying to do by splitting text into lines. Also read about the [XY problem](https://xyproblem.info/). This will save you from many troubles in the future. – akinuri Jul 29 '21 at 19:55