Rendering a page is a complex process. When you change the DOM structure or modify the CSS, the browser needs to respond to that accordingly. By knowing how this operation takes place, you can alter your approach to increase performance. In this article, we cover how the browser renders the page, what are the reflow, paint, and composition processes and how we can optimize the rendering.
The rendering process
The whole process begins when the browser receives the initial HTML from the server. First, the browser creates two trees: Document Object Model (DOM) and CSS Object Model (CSSOM). First one describes your content, and the second one represents the styles.
Creating the Document Object Model
Based on the encoding of your HTML browser reads its raw data and figures out distinct characters. Thanks to that, the browser can understand the sequences of characters that create different tags, like <div> and <img>. In the process called lexing, the tokens receive properties, creating nodes – also called objects.
Since the HTML describes relationships between tags – for example, the <body> is a child of <html> – the objects from the process above make a tree structure. It is called the Document Object Model (DOM) that we can interact with. Each time the browser goes through a snippet of HTML, it begins the process described above. You can look it up in the Developer Tools.
Forming the CSS Object Model
Our CSS code undergoes a similar treatment than HTML. The browser converts it into objects structured in a tree called the CSS Object Model (CSSOM). It needs to be arranged in a way that underlines the relationships between nodes. The browser starts from the most general rule – for example, styles that applied for the whole body – to the most specific rules, cascading down. Let’s take a look into the Developer Tools.
It is essential to understand that processing both HTML and CSS requires computing power, and therefore, it takes time. After the DOM and CSSOM are ready, the next part of the process begins.
Combining DOM and CSSOM into the render tree
The browser combines DOM and CSSOM into a render tree. It contains all displayable DOM content. Referring to it as the visible nodes can be misleading because the render three does not contain any elements with display:none. It does, on the other hand, contain nodes with visibility:hidden because they still take some space on the screen. It is a significant difference that we explore further in this article. With the render three being ready, the browser can proceed to the next stage.
Reflow paint and composite stages
Reflow, also called the layout stage, is responsible by calculating the exact position and size within the browser view. It might happen when we manipulate the DOM tree, or change styles in a way that might affect the layout. The children of the affected element are also reflowed to take into account the new layout of the parent. The siblings are reflowed because they might be moved due to the layout changes. The ancestors of the affected node are also impacted to react to the change in the size of their children. It is quite an expensive operation. It might cause your page to slow down significantly and therefore, it is important to understand how to write our code in a way that it occurs less often.
The next stage is called paint. It is a process of filling in the pixels. It occurs every time the browser needs to apply the visible changes. Examples such properties are background, color, and visibility. Aside from changes in the DOM or CSSOM, it can also be triggered by actions like drawing on a canvas. It is still quite expensive in terms of performance, but not so much as the reflow.
The composite stage is about putting together all the painted parts so that they can be displayed on the screen. During composition, the browser combines all the layers of the elements in the right order.
Introducing the concept of CSS Triggers
Here we experience another crucial difference between the display and the visibility properties. Changing the display involves the reflow stage, and changing the visibility might only result in a repaint. Changing the value of a CSS property needs to be followed up by a reaction of the browser. The computing power required to change CSS properties differ based on how many stages it involves. This concept is sometimes referred to as CSS triggers.
Caring about the performance
There are multiple ways in which we can take care of our rendering performance. If the operation that we want to perform needs to involve the reflow stage, we can improve it by keeping our DOM tree not that deep. Also, reflowing the element that is positioned absolutely, or fixed, might be less expensive because it might not trigger the reflow of the adjacent nodes. Making multiple changes one by one might trigger more than one reflow. It might be a good idea to apply them first to an element that is not displayed so that they are applied all at once. Here is why using solutions like React might help you with improving the performance due to the usage of the Virtual DOM.
Avoiding unnecessary stages
In many cases, we can prevent some of the stages of the rendering process by changing our approach. The above can significantly increase our performance, especially with animations. The fewer stages are involved, the bigger the chances to keep the stable FPS rate. The most important thing is to avoid animating the changes that require the reflow stage. Calculating the geometry – position, and size – of all the elements in every animation frame is very costly. Transitioning properties such as left or right might cause the smoothness of your animation to suffer.
A way better approach would be to use the transform property that does not cause other elements to flow around it. Thanks to that, we can avoid the reflow stage. To see an example, check out this codepen created by css-tricks. This operation can be handled with the use of GPU acceleration to make it even more smooth. You can also hint the browser of the type of transformation you are going to perform on an element with the usage of will-change. Another property that animates well is opacity that the browser can highly optimize.
The above is due to the fact, that transform and opacity can be handled by the compositor thread. It is separate from the main thread where the previous stages occur and where the JavaScript executes. Even if the browser is occupied with some expensive tasks, the animations can still be handled by the compositor thread. If the animation triggers the paint of the layout stages, the main thread is required to work. Feel free to visit the Chromium documentation for more specific information.
Summary
In this article, we’ve gone through the way the browser renders the content that we can see and interact with. Thanks to that knowledge, we can alter our approach to deliver a better experience for our users by increasing the performance and making our websites feel more responsive. With the awareness of the reflow, paint, and the composite stages and the CSS triggers, we can optimize our code to achieve faster rendering. By the application of the above knowledge, our pages load faster, and the animations are smooth.
One thing worth mentioning is while using
transform: translate()
and similar property values, means the area might get not clickable, if there’s an action button hidden underneath an animated element. Devs havet to be careful when implementing animations also from a perspective of UI accessibility.