Violation Long running JavaScript task took xx ms

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 200ms

Chrome 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.

A complex GUI layer in the game during development. The right side has horizontal progress bars and changing the texts (eg.: percentage) freezes the screen thus the moving progress bars for about 200ms ruining the user experience.

Solution

We have two ways to limit boundaries as close as possible:

  1. Use fix width and height size instead of CSS Grid, but c’mon.
  2. 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:

  1. 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.
  2. 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.

Written by
Barnabás Bartha
Barnabas Bartha