I was tasked with getting lines of text from an element many times. Usually it was to truncate the text (Before line-clamp was a thing) or to animate the text line by line.
It sounds easy, but I have encountered many edge cases in practice. After trying out multiple approaches, I finally have a solution I'm satisfied with, although it has some limitations.
Let's start with a demo which displays one of my favorite Frank Zappa quotes:
To restart the animation, resize the element using a handle on the right.
The same code is also available on CodePen for you to play with.
Implementation
The idea is to go word by word and check if the word has fallen into the next line. To determine if a word has fallen, we need to compare the word's top offset with the previous word's top offset. When it changes, it means a new line has started.
We can't get top offset from text, but we can get it from HTML elements. Therefore, we need to wrap each word in a separate <span>
element first. Then we can loop through all the spans and use getBoundingClientRect
to get the top offset value. When the value changes compared to the previous word, it means that the current word fell into a new line.
I'm adding display: inline-block
to each word's span element to prevent a single word from breaking into multiple lines.
Limitations
This approach worked very well for my use case, but it has one main limitation - it works with plain text only. HTML elements will be ignored. I played with implementing support for nested elements as well, but it got complicated quickly, so I never finished it.
Your fonts have to be loaded before running this script. Otherwise it will calculate lines against one font and when the other loads, it may not be correct.
And one last thing, my code does everything inline and replaces the element's text with a bunch of span elements. If you don't want that, you can clone the element, run the code and then remove the clone from DOM.
Examples
I've put a couple of examples below, and as far as I can see, each one works well, no matter the language, alphabet or direction.
Please excuse me if I butchered your language, I just used Google Translate with the original English text.