
Before you read
This is the continuation of this previous article on browsers' internals (JavaScript focused), if you haven't already read it, take 10-minutes and give it a shot (good content inside, I promise ).
The critical importance of delivering 60FPs
If you are just doing simple stuff like CSS animations, simple landing pages, using few and lightweight elements, avoiding fancy / esoteric / satanic things, you may not be interested in this article, you can just code and everything should be fine also in mobile devices.
Instead, if your are dealing with more complex apps, with many time-sensitive and complex animations, with thousands of different and/or heavy elements, being performant and hitting 60FPs (Frame per second) could be hard, this article should be useful in order to have a better understanding on what’s happening, and how to create a better, optimized and performant code.
Why 60FPs? Because with that frame rate the human eye sees the image flow as continuous. Here a practical comparison between 30 and 60 FPS:
In other terms this means that to provide the best user experience to your customers the browser must be able to generate at least 60 frames per second, which is 1 frame every 16ms10ms (the browser has to do also other stuff, so you won't have 16ms all for you).
Rendering what?
The Rendering UI Queue is the process by which browsers manage to transform your abstract code (HTML + CSS + JavaScript) into raster images to show to the end user. This process is sometimes referred also as rendering path or critical rendering path.
This process consists of a long pipeline of different operations needed to interpret the code, respond to
Understanding this process going on behind the scenes is a key part in order to proper analyze, debug and engineer how to structure a complex Front-end application, deal with performance bottlenecks and unexpected frame rate drops, create a fast, performant and smooth user interfaces and experiences.


Let's see the single operations in detail
- DOM parsing: parse the HTML code into a machine-understandable tree, called DOM (Document Object Model);
- CSS parsing: parse the CSS code into another machine-understandable tree,
it seems that computers like trees, I like them as well
;
- CSSOM creation: this is a merge between the previously parsed DOM and CSS, called CSSOM (CSS Object Model). It contains the information of all the DOM elements, along with their styles (critical for the next step, keep reading);
- Render tree creation: this is kind of a cleaned CSSOM, in fact it contains only the DOM elements that will be rendered in the page: if for example some DOMNode is styled with
display: none;
in the CSSOM, it won't be in this tree as it won't be rendered (displayed) on the screen; - Layouting: sometimes also referred as Reflow, this is the process of calculating the sizing and positions of every single element from the Render tree;
- Layer tree creation: it is not granted that there will be only one final raster of the page. The browser (or you using some imperative styling rule ex.
will-change: transform;
) can decide that for the given page it is better to split it into different layers (rasters). This is usually done for performance reasons: if a raster has to be changed multiple (many) times while the others must remain unchanged this can avoid un-needed repaints of un-changed items. This process figures out how many layers need to be created and which elements will fall inside each one; - Painting: at this stage we know everything of our code, everything has been reordered into digestible trees and we are ready to actually paint the pixels in rasters (in Chrome this process is made via the low-level open-source 2D graphic library Skia);
- GPU sync: the fresh painted rasters from the Painting stage are sent to the GPU memory with all the necessary management information;
- Composition: this is the final stage, the GPU positions every single raster in the right position and with the right effects applied (ex.
opacity
), creating the final frame on the screen.
On computational costs and performance
Every frame has to be consistent with the actual code, hence the browser watches for HTML or CSS changes to restart the Rendering pipeline and update the showed frame. The pipeline has not to be executed in his totality, in order to save precious time and gain performance, the browser executes only the strictly needed operations.
For example, if using JavaScript you manipulate the DOM, the browser has to start the pipeline from the first steps, but it you have only added an hidden element, it will skip the Layouting and Painting steps as nothing as changed geometries or visible things (the old frame keeps its validity).
Usually the most time-consuming processes (possible bottlenecks) are the Layouting and Painting steps: the first increases with the number of elements, the latter increases with the size of them and their styling effects (painting a uniform colored background is much less computational expensive than dealing with shadows and gradients!).
TIP: you can use Chrome DevTools to quickly identify paint issues and areas that are constantly re-painted, go to the rendering tab in the DevTools panel and choose “Show paint rectangles”.

Another possible bottleneck for un-experienced JavaScript developers is to inadvertently force a re-layouting or reflow via JavaScript. This happens if you first invalidate the current frame (for example changing the height
of an element), then you read the measures (for example reading the width
of another element).
This way you are trying to read a value from a current invalidated frame, hence the browser has to build a new frame in order the let you read the right (updated) values.
Doing a loop of write/read styles could be a very big error, the best thing is to group all the write/read requests and batch them as a single operation. This issue is sometimes referred also as Layout Trashing.
//The wrong way:
for ( let i = 0; i < 10; i++ ) {
changeHeight();
readWidth();
}
//The right way:
//Using two loops is way more performant than forcing reflows!
for ( let i = 0; i < 10; i++ ) {
//Do all the changes first.
changeHeight();
}
for ( let i = 0; i < 10; i++ ) {
//Read all them after.
readWidth();
}
Using composition-phase-only properties 
These properties are the best to be used and manipulated for animations and interactions, in fact they trigger only the composition phase of the Rendering pipeline. This way they assure to be super-fast and deliver 60+Frame/s using GPU-accelerated operations only.
Following a list of the most common CSS properties and their invasiveness in the rendering process:
CSS Property | Invasiveness |
opacity | composite |
cursor | composite |
z-index | composite |
user-select | composite |
transform | composite |
color | paint |
border-style | paint |
visibility | paint |
background | paint |
text-decoration | paint |
background-image | paint |
background-position | paint |
background-repeat | paint |
outline-color | paint |
outline | paint |
outline-style | paint |
border-radius | paint |
border-radius | paint |
box-shadow | paint |
outline-width | paint |
box-shadow | paint |
background-size | paint |
CSS Property | Invasiveness |
width | layout |
height | layout |
padding | layout |
margin | layout |
display | layout |
border-width | layout |
border | layout |
top | layout |
position | layout |
font-size | layout |
float | layout |
background-color | layout |
border-color | layout |
text-align | layout |
overflow-y | layout |
font-weight | layout |
overflow | layout |
left | layout |
font-family | layout |
margin-bottom | layout |
padding-bottom | layout |
line-height | layout |
vertical-align | layout |
right | layout |
clear | layout |
white-space | layout |
list-style-type | layout |
bottom | layout |
zoom | layout |
font-style | layout |
font | layout |
min-height | layout |
max-width | layout |
min-width | layout |
list-style | layout |
content | layout |
border-collapse | layout |
text-shadow | layout |
box-sizing | layout |
text-indent | layout |
border-horizontal-spacing | layout |
border-spacing | layout |
max-height | layout |
text-transform | layout |
text-overflow | layout |
word-wrap | layout |
letter-spacing | layout |
appearance | layout |
direction | layout |
Further readings
Avoid Large, Complex Layouts and Layout Thrashing!
Simplify Paint Complexity and Reduce Paint Areas
Rendering Queue CSS Triggers
High Performance Animations
For updates, insights or suggestions, feel free to post a comment below!