Calculate HTML Element width before render
Steven Straatemans
Posted on February 19, 2021
I want to show you a small trick to know the size of an element, before rendering that element on the screen.
This trick can be useful for various reasons. Developers had to use this trick a lot more in the old days, when we didn't have things like flex and CSS grid and elements couldn't size themselves the way we want them too.
Sometimes you had to calculate the size of your element and set the width by hand.
I hadn't used this trick in ages. But I ran across a small story in a recent project which made me think of this. I had tried all other CSS tricks. I asked my colleagues, but no working solution was suggested.
And after much frustration I thought of this option.
So what was the problem?
I had to build a small component which showed 2 lines of text. So that when the user clicks the "Read more" button it will expand and show all the text.
That doesn't sound complicated at all, does it? Just show X amount of words or Y amount of characters. What are you complaining about?
Well the designer wanted to show 2 lines of text. and at the end of line 2, show the "Read more" button. Like this:
We thought it was a minor task, and I think we did not even bother to poker it. Just a simple chore in a larger story.
And if we would have put the button on the next line, I wouldn't be writing this article. I would just check some line-height and set the overflow of your text element to hidden and be done with it.
But the button really, REALLY had to be at the end of that second line. Designers, right? Right?
You can't fix that with CSS. I first thought of using float: right;
But I would still need to know where to put the element to float. Adding it to the end of the text, would hide the button element.
Somehow, we needed to find a way to know how many words we can fit on that line and also have enough space to accomodate the button.
Ok, so what is the solution?
The easiest way to find out how many words we can fit on those two lines is to throw in one word at a time to see if it fits. And once we go over the two lines, we stop. Easy.
We create a temporary element and append it to the element which will contain the actual text. Because it is a child it will inherit all the styles from our original text element, so all the text will have the correct font-size and line-height etc.
We fill that element word by word and see if the words fit on our two lines ( + our button). And when we go over the two lines, we stop. Once we have the correct amount of text we can remove our temporary element.
Now that we have the correct amount of text that can fit, we copy that part of the text to the original text element that is visible on your screen. And it will have your button behind it, just the way we planned it.
Our function will look something like this:
const createMaxLines = () => {
// create the temporary Element
const ruler = document.createElement('div');
ruler.style.width = 'auto';
ruler.style.position = 'absolute';
ruler.style.whiteSpace = 'nowrap';
// Adding the element as a child to myElement.
// it will be added to the DOM
myElement.appendChild(ruler);
/**
* Do the calculations you need to do
*/
// clean up after yourself
myElement.removeChild(ruler);
};
Will that not cause some weird flickering on your screen?
You would think so. We are creating an element. We are adding it to the DOM. It's why I made the temporary element invisible(with CSS) in my first version.
But... The whole function, that checks what text should be visible on our screen, is synchronous. And there are a couple of things that are happening.
But before I can explain that, we first need to look at the process of the render engine in the browser.
There are a couple of steps that need to be taken before an element is shown on your screen.
I will not go into complete detail here, it's to big of a subject, but if you want to learn more in depth about the rendering process, you definitely need to read this article from Tali Garsiel and Paul Irish. It's an oldy, but still awesome.
So first the DOM tree is created, containing a tree with all our HTML tags. Also the CSS is parsed in such a tree.
These two are combined in the render tree, where styles and elements are combined.
The next step is the layout or reflow, where all elements will receive their position.
And finally the paint stage, where the elements will appear on the screen.
Now every time an element is added to the DOM, like in our function, the position of all elements needs to be recalculated in the layout/reflow stage. When that stage is done the screen will be repainted.
Like I said read the article mentioned above for details, what I described here was a gross over-simplification.
As soon as our temporary element is added to the DOM it will trigger a reflow of the render engine.
Now, every time a word is added to the element another reflow is triggered. BUT...not a repaint. The repaint will occur at the end of our function when every calculation is done. And this is the important part, because it is the repaint that will make everything appear on you screen. But at the end of our function, we will remove the temporary element from our DOM, again causing a reflow. Only after that reflow, will the paint part of the render engine run. And because our temporary element is not in the DOM anymore, it will not appear on our screen.
How about performance?
You shouldn't try it with the whole content of "War and Peace", but this option is usually done with just a couple of lines of text and that should be fine.
You can probably improve the performance somewhat, by using a better algorithm to determine how many words will fit.
Conclusion
This is a nice little trick if you need to calculate the size of your element before it will show on your screen.
You will not need it much, because most scenarios nowadays, you can solve with CSS. But for those rare occasions CSS can't help you out, this might do the trick.
Would love to hear from you when you used it in one of your own projects.
I created a small react-component for it, so if you're curious you can find the code here and example here
Posted on February 19, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.