Split Text into Lines with React for animation purposes

Viewed 1362

I'm trying to create a function that would accept a string and split it into separate lines so that each "line" would fit into reference container without wrapping. Later I'll use this output to animate each line reveal.

Currently I'm using this library but I'm struggling with few issues, so I'm to build my own solution.

Library: https://cyriacbr.github.io/react-split-text/

https://github.com/CyriacBr/react-split-text

My issues with it:

  • it triggers a rerender after height of the viewport changes ( so on mobile, thats every time a browser menu pops up )
  • it measures line width based on single span width, but I'm using non monotype font, so its not accurate and causes weird issues

My idea is that I would make a component ( or a hook ), that would accept a string, and return array of lines (strings).

It would work sth like this:

  • split text into words
  • render each word in a ghost component, so that I would be able to measure each word by adding a ref to it
  • measure container width
  • group words into array of "lines" that would fit into container without wrapping.

So far I've came up with this solution:
https://codesandbox.io/s/buggy-line-splitter-c2j7b

It kinda works, but it's buggy.

Issues with my solution:

  • my lines are wider than they should be, text wraps but it shouldn't.

  • i could "fix" issue above by multiplying container width by .9, but I feel that its gonna break sooner or later.

Do you have any ideas how I could improve my solution? Thanks

////////////////////////////////////////////////

I have found the answer to my problem!

Expanding on @Will idea, there is thread with solution to the problem:

Weird issue with styles and element measurement (scrollWidth / scrollWidth) in Gatsby

1 Answers

Going off of this answer, you can make a div and throw text into it, checking the scrollWidth compared to the clientWidth. When it overflows, stash the previous tested text (that didn't overflow) and start a new line.

A more clever person could probably re-write it to use fewer arrays, but it seems to work to split a paragraph of text into lines that should fit. Style as needed with padding, etc, to make it fit the thing you eventually animate.

const input = "I'm baby kickstarter authentic kinfolk PBR&B post-ironic live-edge readymade truffaut tousled activated charcoal etsy. Schlitz marfa yuccie heirloom yr, cornhole single-origin coffee master cleanse fixie tumblr street art edison bulb shoreditch. Keytar tousled hell of, XOXO selfies vegan hot chicken keffiyeh sriracha roof party jean shorts activated charcoal. Readymade flexitarian tbh, iceland health goth poutine wolf 8-bit put a bird on it street art vice mixtape kickstarter. Viral messenger bag kale chips sriracha chillwave.".split(" ");

const testEl = document.getElementById("test");
testEl.style.width = "200px"; // <-- desired width

let line = [];
let testLine = [];
let output = [];

input.forEach(word => {
  testLine.push(word);
  testEl.innerHTML = testLine.join(" ");
  if (testEl.scrollWidth > testEl.clientWidth) {
    output.push(line.join(" "));
    testLine = [];
    line = [];
  }
  line.push(word);
});

console.log(output);
#test {
  display: hidden;
  height: 0;
  overflow-x: scroll;
  white-space: nowrap;
}
<div id="test" />

Related