2

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

Toster
  • 57
  • 1
  • 7
  • 1
    Does this question help? https://stackoverflow.com/questions/1966441/how-to-select-nth-line-of-text-css-js - I notice oned answer there that recommends this lining.js library, that achieves what you're trying to do: http://zencode.in/lining.js/ – Scotty Jamison Jul 06 '21 at 23:32
  • Thats a nice alternative, however i would like to animate my elements with framer motion, so I need an array of "lines" that I would later map through to create "animated" elements. Still, thank you for your answer! – Toster Jul 06 '21 at 23:55

1 Answers1

2

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" />
Will
  • 3,201
  • 1
  • 19
  • 17
  • Wow, thats smart. I'll try it out! Thanks – Toster Jul 06 '21 at 23:57
  • So i have been playing with this idea for a while now, but I'm still having issues with too long lines / wrapping content. I have made this codesandbox with your solution ( a little bit modified ) I'm probably missing something simple. Do you have an idea what is the issue here? https://codesandbox.io/s/line-splitter-bfeej?file=/src/LineSplitter.jsx – Toster Jul 07 '21 at 01:45
  • 1
    Good catch about the bug that dropped the last line. I messed with your component quite a bit, I think I got it working: https://codesandbox.io/s/line-splitter-forked-e408r?file=/src/LineSplitter.jsx – Will Jul 07 '21 at 16:26
  • Your solution works perfectly fine in a pen however when I'm trying to use the same component in my Gatsby project I'm facing weird issues. Component works well when my app is using default font, however if I change it to anything else its no longer accurate and lines overflows. I have tried to use both styled components and plain css for styling but no luck. What's weird is that after a few refreshes it sometimes works correctly. I think that styles are applied after function runs and that causes overflows, but idk. Do you have and ideas how i could fix that? – Toster Jul 08 '21 at 03:03
  • The font in the test box has to also be the same as the one that finally gets displayed. With the component I wrote, the div is never attached to the document so maybe it depends upon what selector is used for the font rule? – Will Jul 08 '21 at 14:19
  • I know, I was trying it out with same selectors so thats not the case unfortunately. I was also using exactly same code in both Gatsby and Create React App projects, and in CRA it works fine, while in Gatsby it overflows. ( but works fine with default font, so it seems like in Gatsby styles are applied differently even with same files and the same imports as in CRA ) – Toster Jul 08 '21 at 15:16
  • I have found out the issue. Component works perfectly fine with fonts installed locally, and thats the font-face css declaration that breaks it. I have made a code pen with Gatsby build ( I have also tested it on local machine ). Code breaks whenever i import new font of use @font-face, both with fonts downloaded and stored locally. https://codesandbox.io/s/gatsby-line-splitter-test-bug-5m05q?file=/src/components/styles.css Weirdly enough, its broken only on first load, and after refresh it works fine. Not on the local machine though – Toster Jul 08 '21 at 17:13