Direct DOM manipulations are expensive.

Careful, it comes at a cost

Abhijit Hycinth
Technogise
6 min readJun 21, 2020

--

Before we start, let’s give a definition to the DOM. “It is an abstract representation of structured text”, where the structured text is our HTML code.

And to understand how DOM manipulations are expensive, we’ll need to take a little peek into what a rendering engine typically goes through to paint the UI.

Different browsers use different rendering engines:

  • Firefox uses Gecko.
  • Safari uses WebKit.
  • Chrome and Opera (from version 15) use Blink, a fork from WebKit.
  • Microsoft’s Edge is based on Chromium which also uses Blink.

The following diagram illustrates the architecture of WebKit for painting the DOM.

WebKit mainflow

WebKit is an open source rendering engine. More on it here.

I know, all of this can be overwhelming. But just bear with me and allow me to walk you through the step by step.

Parser

It simply parses your HTML and CSS to form the DOM and CSSOM trees.

  • DOM tree contains the content i.e the data to be displayed with respect to each node.
DOM tree with information to be displayed on each node
  • CSSOM tree contains the style rules (appearance/presentation) needed to be applied to each node.
CSSOM with style rules.

Attachment

This is a process in the rendering engine that combines the above generated DOM and CSSOM trees to form the render tree.

  • Render tree: This is the final tree that contains the required information regarding what needs to be displayed with the styles combined.
Render tree with content and styles combined

Note how the head, meta, html etc. tags have been removed.

Up until now, we have produced the DOM tree and CSSOM tree and combined them to form the render tree. Hence, we have information on what needs to be displayed and what style rules need to be applied.

Reflow

Also known as the layout stage, in this we capture the exact size and positions of the elements with respect to the viewport.

We still need to figure out the positions and the coordinates of these elements and their size with respect to the viewport.

This is where reflow comes into the picture. We start traversing the root node of the render tree and calculate the positions and sizes of each element including the child and adjacent elements, inferring from the style rules.

Note: This an expensive process, since it requires traversing the child and adjacent elements.

The resultant is a box model, that contains information to exact pixels for each element.

The resultant box model of the layout stage.

Painting

Now that we know which elements need to be displayed and their computed styles and geometry, we come to the final stage which takes the nodes of the render tree and converts them to actual pixels onto the screen. This process is known as Painting.

The browser takes its time to paint the elements in the render tree and requires some of the processing capabilities of the CPU.

Phew, now that we’ve walked through the basic flow of rendering in a browser, let’s look at some of its problems and when do they occur.

Re-rendering can be expensive

The time required to perform render tree construction, layout and paint varies based on:

  • size of the document
  • applied styles
  • the device it is running on

The larger the document, the more reflow the browser has. The more complicated the styles, the more time is taken for painting. For example, a solid colour is “cheap” to paint, while a drop shadow is “expensive” to compute and render.

There are 2 major things that make DOM and CSSOM expensive. Repaint and Reflow.

Repaint

A repaint also known as redraw occurs when changes are made to an element’s skin that changes visibility i.e something is made visible which was previously not, or vice-versa.

Note: A repaint accounts for changes only in the visibility of the element and not for changes in the layout.

Some examples are:

  • Adding an outline to an element
  • Changing the background colour
  • Changing the visibility style

Repaint is expensive in terms of performance, as it requires the engine to search through all elements to determine what is visible, and what should be displayed.

Reflow

A reflow happens whenever there is a change in the position or dimensions of an element.

Reflow is even more critical to performance since a reflow of an element leads to the re-calculation of positions and dimensions of all child elements, adjacent elements and in some cases the parent elements (depending on the browser). This leads to re-rendering part or all of the document.

For example:

In the HTML snippet above, a reflow on the paragraph would trigger a reflow of the strong because it is a child node. It would also cause reflow of the ancestors (div.navbar and body — depending on the browser). In addition, the h5 and ol would be reflowed simply because they follow that element in the DOM.

Reflows are very expensive in terms of performance, and is one of the main causes of slow DOM scripts, especially on devices with low processing power, such as phones. In many cases, they are equivalent to laying out the entire page again.

What causes a reflow?

A lot of things actually, some of which are:

Taking measurements

Taking measurements of an element forces a reflow so that the measurements are accurate.

The render tree is flushed whenever we use any of the methods like offsetWidth, offsetHeight , getComputedStyle .etc.

The repaint may or may not be visible but the reflow has to happen behind the scenes for accurate measurements.

Multiple DOM changes

Each DOM modification will trigger a reflow. Be it adding a new element, changing the value or various attributes of a node will all be enough to cause a reflow.

Making several changes one after the other may trigger more than one reflow. Hence it is always recommended to make multiple changes in a non-displayed DOM fragment.

{display: none } vs {visibility:hidden}

An element with display style none would be removed from the render tree. Therefore, any changes to the node would not cause any reflow.

On the contrary, an element with visibility style hidden would still be a part of the render tree.
Hence a change to hidden node or its children would still cause a reflow.

However, using {display: none} can be useful in many ways. We can make multiple DOM changes to the element and make it visible once all the changes are done.

In the above snippet, once the element is made visible, all the DOM changes get applied in one reflow.

Unlike the previous solution, this method has two additional reflows for hiding and showing the element.

Wrapping it all

There are many other scenarios that can trigger reflow or repaint, and each has its own way of tackling it. Most of them can be optimised by avoiding unnecessary DOM depths and minimising CSS rules. However, modern tools like ReactJS solve this problem by using virtual DOM instead of direct DOM manipulation.

References:
https://www.html5rocks.com/en/tutorials/internals/howbrowserswork/

https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction

https://dev.opera.com/articles/efficient-javascript/?page=3#reflow

http://www.stubbornella.org/content/2009/03/27/reflows-repaints-css-performance-making-your-javascript-slow/

https://en.wikipedia.org/wiki/Flash_of_unstyled_content

--

--