Briefly
To draw the result of our code on the screen, the browser needs to perform several stages:
- First, it needs to download the source files.
- Then it needs to read and parse them.
- After that, the browser begins rendering.
Each of these processes is very complex, and we will not examine them in great detail.
We will only pay attention to the details that frontend developers need to know to better understand why different solutions affect performance and rendering speed differently.
Let's start in order.
Resource Retrieval, Fetching
The browser obtains resources by making requests to the server. In response, it may receive data in the form of JSON as well as images, videos, style files, and scripts.
The very first request to the server is usually a request for the HTML page (most often index
).
Its code contains links to other resources that the browser will also request from the server:
<!DOCTYPE html><html lang="en"> <head> <link href="/style.css" rel="stylesheet"> <title>Document</title> </head> <body> <img src="/hello.jpg" alt="Hello!"> <script src="/index.js"></script> </body></html>
<!DOCTYPE html> <html lang="en"> <head> <link href="/style.css" rel="stylesheet"> <title>Document</title> </head> <body> <img src="/hello.jpg" alt="Hello!"> <script src="/index.js"></script> </body> </html>
In the example above, the browser will also request:
- the stylesheet file
style
;. css - the image
hello
;. jpg - and the script
index
.. js
Parsing
As the HTML page is being downloaded, the browser tries to "read" it — parse it.
DOM
The browser works not with markup text, but with abstractions over it. One of those abstractions, the result of parsing HTML code, is called DOM.
DOM (Document Object Model) is an abstract representation of an HTML document, through which the browser can access its elements, modify its structure, and style.
The DOM is a tree. The root of this tree is the HTML element, and all other elements are child nodes.
For such a document:
<html> <head> <meta charset="utf-8"> <title>Hello</title> </head> <body> <p class="text">Hello world</p> <img src="/hello.jpg" alt="Hello!"> </body></html>
<html> <head> <meta charset="utf-8"> <title>Hello</title> </head> <body> <p class="text">Hello world</p> <img src="/hello.jpg" alt="Hello!"> </body> </html>
...we get such a tree:

While the browser parses the document and builds the DOM, it encounters elements like <img>
, <link>
, <script>
, which contain links to other resources.
If a resource is non-blocking (for example, an image), the browser requests it in parallel with parsing the rest of the document. Blocking resources (like scripts) pause processing until they are fully loaded.
We can tell the browser how exactly it should request some resources, for example, scripts. This can be useful when in the script we are going to work with elements that are located in the markup after the <script>
tag:
// script.jsconst image = document.getElementById('image')
// script.js const image = document.getElementById('image')
<body> <script src="script.js"></script> <img src="/hello.jpg" alt="Hello world" id="image"></body>
<body> <script src="script.js"></script> <img src="/hello.jpg" alt="Hello world" id="image"> </body>
In this case, image
because the browser has only managed to parse part of the document before this <script>
tag.
But everything is fine here, the image will be found:
<body> <img src="/hello.jpg" alt="Hello world" id="image"> <script src="script.js"></script></body>
<body> <img src="/hello.jpg" alt="Hello world" id="image"> <script src="script.js"></script> </body>
And this is also fine, the defer
attribute will tell the browser to continue parsing the page and execute the script later:
<body> <script src="script.js" defer></script> <img src="/hello.jpg" alt="Hello world" id="image"></body>
<body> <script src="script.js" defer></script> <img src="/hello.jpg" alt="Hello world" id="image"> </body>
CSSOM
When the browser finds a <link>
element that points to a stylesheet file, it downloads and parses it. The result of parsing the CSS code is the CSSOM.
CSSOM (CSS Object Model) is, like the DOM, a representation of style rules in the form of a tree.
For the document above with such styles:
body { font-size: 1.5rem;}.text { color: red;}img { max-width: 100%;}
body { font-size: 1.5rem; } .text { color: red; } img { max-width: 100%; }
...we get such a tree:

Reading styles pauses the reading of the page's code. Therefore, it is recommended to deliver only critical styles at the very beginning — which are present on all pages and specifically on this one. This reduces the waiting time until the "page loads."
Render Tree
After the browser establishes the DOM and CSSOM, it combines them into a single render tree.
Render Tree is a term used by the WebKit engine; it may differ in other engines. For example, Gecko uses the term Frame Tree.
As a result, for our document above, we will get such a tree:

Note that only visible elements are included in the Render tree. If we had an element hidden by display
, it would not be included in this tree. We will discuss this in more detail later.
The overall parsing scheme looks like this:

In the early steps, we deal with HTML and CSS, and then combine them into the Render Tree.
Position Calculation and Sizing, Layout
After the browser has the render tree, it begins to "place" elements on the page. This process is called Layout.
To understand where each element should be located and how it influences the positioning of other elements, the browser recursively calculates the sizes and positions of each element.
The calculation starts from the root element of the render tree, which has dimensions equal to the viewport size. The browser then proceeds to each of the child elements in turn.
It is important to remember that Layout is based on a flow model. This means that if elements do not affect the positioning and sizing of other elements, then their position and sizes can be calculated in one pass.
That is why it is recommended to "stay in the flow" when laying out layouts — so the browser does not have to recalculate the same element multiple times, allowing the page to be rendered faster.
Global and Incremental Layout
Global Layout is the process of calculating the entire tree completely, that is, every element. Incremental calculates only part of it.
Global Layout is triggered, for example, when the window size changes because the browser needs to adjust the entire page to the new screen size. This is a very costly process.
Incremental Layout recalculates only the "dirty" elements.
"Dirty" Elements
These are the elements that have been modified and their child elements.
If we change a block, the browser will repaint it and its children, because their position and sizes may depend on the parent.

Then the browser begins the actual rendering.
Actual Rendering, Paint
During rendering (Paint), the browser fills in the pixels on the screen with the necessary colors depending on what should be drawn in a specific place: text, image, background color, shadows, borders, etc.
Rendering can also be global and incremental. To determine what part of the viewport needs to be redrawn, the browser divides the entire viewport into rectangular sections. The logic here is the same as in Layout — if changes are limited to one section, only that section will be marked "dirty" and redrawn.
Rendering is the most expensive process of all that we have mentioned.
Order of Rendering
The order of rendering is associated with the stacking context.
In general terms, rendering starts from the background and gradually moves to the foreground:
background
;- color background
;- image border
;children
;outline
.
CPU and Compositing
Both Layout and Paint work via the CPU (central processing unit), which makes them relatively slow. Smooth animations under such conditions are incredibly costly.
For smooth animations, browsers use compositing.
Compositing is the separation of a page's content into "layers" that the browser will redraw. These layers are independent of each other, which means that changing an element in one layer does not affect elements in other layers, and they do not need to be redrawn.
Because of the separation of elements into different compositional layers, the transform
property does not load the browser as much. Therefore, to prevent animations from lagging, it is recommended to use transform
and opacity
.

Using properties like transform
, for example, "pulls" an element onto a separate composite layer, where its position does not depend on others and does not affect them.
Reflow and Repaint
The rendering process is cyclical. The browser redraws the screen every time some changes occur on the page.
If, for example, a new node is added to the DOM tree or the text is changed, the browser will build a new render tree and restart position calculation and rendering.
One update cycle is one animation frame.
Knowing the browser's "rendering schedule," we can "warn" it that we want to start some animation for each new frame. This can be done using request
.
const animate = () => { // Animation code}
const animate = () => { // Animation code }
This function starts a new animation frame: it updates some property or redraws the canvas.
If we want to achieve smooth animation using the function above, we need to ensure an average of 60 screen updates per second (60 fps — frames per second).
This can be done crudely through an interval:
// 60 times in 1000 milliseconds, approximately 16 ms.const intervalMS = 1000 / 60setInterval(animate, intervalMS)
// 60 times in 1000 milliseconds, approximately 16 ms. const intervalMS = 1000 / 60 setInterval(animate, intervalMS)
Or use window
:
window.requestAnimationFrame(animate)
window.requestAnimationFrame(animate)
Intervals do not always run at the right moment. set
does not consider what stage the rendering is at, and as a result, rendering frames may be torn or jerky.
With an interval, animation may be choppy because rendering can be triggered at an inappropriate time.
And if the tab is inactive, the interval may "try to catch up," and multiple frames may run at once:

With request
, animation is smoother because the browser knows that in the next frame it needs to run a new animation frame.
It does not guarantee that the animation will be launched exactly once every 16 ms, but the value will be close enough.

In practice
Advice 1
For dynamics, always use transform
and opacity
, avoid changing other properties (like left
, top
, margin
, background
and so on).
This way, you will give the browser the opportunity to optimize rendering, which will make the page more responsive.
For animations that need to be redrawn on every frame, use request
.
This will make heavy animations less choppy.