Recently, while I was working on my webgl sandbox side project‘s GUI layer, I noticed that the screen is lagging and freezes for 150-300ms every time just when a text is changed. It was hard not to notice and ruined the user experience of the GUI.
[Violation] Long running JavaScript task took 200msChrome Console’s warning
The Issue
Looking at Chrome’s Performance Profiler, simply just changing the innerText property of an element triggered a Layout drawing task that took about 200ms, depending on the DOM structure complexity.
There is a long list of properties that forces layout reflow when used or updated and innerText is included. Of course, the browser need to repaint the area where the text element may take place. This area can be limited with fixed width/size and overflow: hidden property, but without a parent like that, this propagates back to the top of the root, so the whole document will be recalculated. Ouch.
Solution
We have two ways to limit boundaries as close as possible:
- Use fix width and height size instead of CSS Grid, but c’mon.
- Write a small hack that reads and computes the size of the panels and sets the same size as fix width and height once we open the GUI layer as follows:
requestIdleCallback(() => { for (const element of document.getElementsByClassName("panel")) { let width = 0; let height = 0; fastdom.measure(() => { const computedStyle = getComputedStyle(element); width = element.clientWidth - parseFloat(computedStyle.paddingLeft) - parseFloat(computedStyle.paddingRight); height = element.clientHeight - parseFloat(computedStyle.paddingTop) - parseFloat(computedStyle.paddingBottom); }); fastdom.mutate(() => { element.style.width = width + "px"; element.style.height = height + "px"; element.style.overflow = "hidden"; }); } });
Two minor additional improvements to make this even better:
- Use requestIdleCallback, because we don’t need this immediately, it’s okay if the browser waits for a moment until it has the capacity to run our script.
- Using fastdom’s measure and mutate function, we can prevent the browser unnecessarily recalculating/updating the layout inside the Animation Frame a.k.a. Layout Trashing.
Result
Voilà, the purple Layout blocks indicating unnecessary drawings are gone and now the GUI works at smooth 60fps again.
Conclusion
As a conclusion, one must keep in mind what calculations and layout drawings are being triggered while using DOM elements. Of course, we don’t bump into this issue in case of a few elements or with simple HTML structure but the given example shows what we have to face with having a complex and deep DOM tree with blurred background in addition.