Understanding Repaint and Reflow in JavaScript
Recently(edit: back in 2019), while researching what makes React’s virtual DOM so fast, I realized how little are we aware of javascript performance. So I’m writing this article to help raise awareness about Repaint and Reflow and JavaScript performance in general.”
Before we dig deeper, do we know how a browser works?
A picture is worth a thousand words. So, let’s have a high-level view of how a browser works!
hmm… what’s “browser engine” and “rendering engine”?
The primary job of a browser engine is to transform HTML documents and other resources of a web page into an interactive visual representation on a user’s device.
Besides “browser engine”, two other terms are in common use regarding related concepts: “layout engine” and “rendering engine”. In theory, layout and rendering (or “painting”) could be handled by separate engines. In practice, however, they are tightly coupled and rarely considered separately.
let’s understand how browsers draw a user interface on the screen.
When you hit enter on some link or URL browser make an HTTP request to that page and the corresponding server provides (often) HTML document in response. (a hell lot of things happen in between)
- The browser parses out the HTML source code and constructs a DOM tree a data representation where every HTML tag has a corresponding node in the tree and the text chunks between tags get a text node representation too. The root node in the DOM tree is the
documentElement
(the<html>
tag) - The browser parses the CSS code, makes sense of it. The styling information cascades: the basic rules are in the User-Agent stylesheets (the browser defaults), then there could be user stylesheets, author (as in the author of the page) stylesheets - external, imported, inline, and finally styles that are coded into the
style
attributes of the HTML tags - Then comes the interesting part — constructing a render tree. The render tree is sort of like the DOM tree but doesn’t match it exactly. The render tree knows about styles, so if you’re hiding a
div
withdisplay: none
, it won't be represented in the render tree. Same for the other invisible elements, likehead
and everything in it. On the other hand, there might be DOM elements that are represented with more than one node in the render tree-like text nodes for example where every line in a<p>
needs a render node. A node in the render tree is called a frame, or a box (as in a CSS box, according to the box model). Each of these nodes has the CSS box properties - width, height, border, margin, etc - Once the render tree is constructed, the browser can paint (draw) the render tree nodes on the screen
Here is a snapshot of how browser draws user interface on the screen.
Want to read this story later? Save it in Journal.
It happens in the fraction of seconds that we don’t even notice that all this happened.
Look closely.
How browser drawing the layout and trying to detect root element, siblings and it’s child element as a node comes and rearranging it’s layout accordingly.
Let’s take one example
<html>
<head>
<title>Repaint And Reflow</title>
</head>
<body>
<p>
<strong>How's The Josh?</strong>
<strong><b> High Sir...</b></strong>
</p>
<div style="display: none">
Nothing to display
</div>
<div><img src="..." /></div>
...
</body>
</html>
The DOM tree that represents this HTML document basically has one node for each tag and one text node for each piece of text between nodes (for simplicity let’s ignore the fact that whitespace is text nodes too) :
documentElement (html)
head
title
body
p strong
[text node] p
strong
b
[text node]
div
[text node]
div
img
...
The render tree would be the visual part of the DOM tree. It is missing some stuff — the head and the hidden div, but it has additional nodes (aka frames, aka boxes) for the lines of text.
root (RenderView)
body
p
line 1
line 2
line 3
...
div
img
...
The root node of the render tree is the frame (the box) that contains all other elements. You can think of it as being the inner part of the browser window, as this is the restricted area where the page could spread. Technically WebKit calls the root node RenderView
and it corresponds to the CSS initial containing block, which is basically the viewport rectangle from the top of the page (0
, 0
) to (window.innerWidth
, window.innerHeight
)
Figuring out what and how exactly to display on the screen involves a recursive walk down (a flow) through the render tree.
Repaint and Reflow
There’s always at least one initial page layout together with paint (unless, of course, you prefer your pages blank :)). After that, changing the input information which was used to construct the render tree may result in one or both of these:
- parts of the render tree (or the whole tree) will need to be revalidated and the node dimensions recalculated. This is called a reflow, or layout, or layouting. Note that there’s at least one reflow — the initial layout of the page
- parts of the screen will need to be updated, either because of changes in geometric properties of a node or because of stylistic change, such as changing the background color. This screen update is called a repaint, or a redraw.
Repaints and reflows can be expensive, they can hurt the user experience, and make the UI appear sluggish
Repaint
As the name suggests repaint is nothing but the repainting element on the screen as the skin of element change which affects the visibility of an element but do not affects layout.
Example.
1. Changing visibility of an element.
2. Changing an outline of the element.
3. Changing background.
Would trigger a repaint.
According to Opera, the repaint is an expensive operation as it forces the browser to verify/check the visibility of all other dom nodes.
Reflow
Reflow means re-calculating the positions and geometries of elements in the document, for the purpose of re-rendering part or all of the document. Because reflow is a user-blocking operation in the browser, it is useful for developers to understand how to improve reflow time and also to understand the effects of various document properties (DOM depth, CSS rule efficiency, different types of style changes) on reflow time. Sometimes reflowing a single element in the document may require reflowing its parent elements and also any elements which follow it.
Virtual DOM VS Real DOM
Every time the DOM changes browser needs to recalculate the CSS, do a layout and repaint the web page. This is what takes time in real dom.
To minimize this time Ember uses key/value observation technique and Angular uses dirty checking. Using this technique they can only update changed dom node or the node which are marked as dirty in case of Angular.
If this was not the case then you are not able to see a new email as soon as it comes while writing a new email in Gmail.
But, browsers are becoming smart enough nowadays they are trying to shorten the time it takes to repaint the screen. The biggest thing that can be done is to minimize and batch the DOM changes that make repaints.
The strategy of reducing and baching DOM changes taken to another level of abstraction is the idea behind React’s Virtual DOM.
What makes React’s virtual DOM so fast?
React doesn’t really do anything new. It’s just a strategic move. What it does is It stores a replica of real DOM in memory. When you modify the DOM, it first applies these changes to the in-memory DOM. Then, using it’s diffing algorithm, figures out what has really changed.
Finally, it batches the changes and call applies them on real-dom in one go. Thus, minimizing the re-flow and re-paint.
I also wrote
If you like it, please leave a comment below — it encourages me to write more.
If you didn’t like it, still drop a comment — explaining how can I improve.
Follow me on Twitter for more updates.
📝 Save this story in Journal.
👩💻 Wake up every Sunday morning to the week’s most noteworthy stories in Tech waiting in your inbox. Read the Noteworthy in Tech newsletter.