Reflow and Repaint
At the beginning, let's review the basic knowledge of reflow and redrawing introduced in the previous section (skip students, please consciously go back to the previous section to supplement → → →).
Reflow: when our modification of DOM causes changes in the geometric dimensions of DOM (such as modifying the width, height or hidden elements of elements), the browser needs to recalculate the geometric attributes of elements (the geometric attributes and positions of other elements will also be affected), and then draw the calculation results. This process is called reflow (also known as rearrangement).
Redrawing: when we modify the DOM to change the style without affecting its geometric properties (such as changing the color or background color), the browser does not need to recalculate the geometric properties of the element and directly draw a new style for the element (skipping the reflow link shown in the above figure). This process is called redrawing.
From this, we can see that redrawing does not necessarily lead to backflow, and backflow will certainly lead to redrawing. If you insist on comparison, you can do more things and bring more expenses. But in the final analysis, these two are food performance, so they are not good stubbles. In our development, we should start from the code level and minimize the number of reflow and redrawing as much as possible.
What actual operations will cause reflow and redraw
To avoid backflow and redrawing, the most direct way is to avoid DOM operations that may lead to backflow and redrawing. Just like when bomb disposal experts solve a bomb, the most important thing is to extinguish its fuse.
The "fuse" that triggers redrawing is easier to identify - as long as DOM operations that do not trigger reflow but trigger style change will cause redrawing, such as background color, text color, visibility (visibility here specifically refers to operations that do not change the position and existence of elements, such as visibility: hidden, and only focus on visibility, which should be distinguished from display:none), etc. To this end, we should focus on understanding the operations that may trigger backflow.
Reflow "fuse"
The most expensive operation: changing the geometric properties of DOM elements
This change can almost be called "pulling one to start the whole body" - when the geometric attributes of a DOM element change, the geometric attributes of all its related nodes (such as parent-child nodes, brother nodes, etc.) need to be recalculated, which will bring a huge amount of calculation.
Common geometric attributes include width, height, padding, margin, left, top, border and so on. We will not list them one by one here. Some articles like to list attribute tables, but I believe I will list them today, and you won't read them or remember them (because there are too many). I won't remember this myself - in fact, it's really unnecessary to remember whether an attribute is a geometric attribute and whether it will lead to changes in the spatial layout. When you write the style, you can see it through the code effect. It's useless to say more. I hope you can write more and try more to form your own "muscle memory".
Operation of "moderate price": change the structure of DOM tree
This mainly refers to operations such as node increase / decrease and movement. The process of browser engine layout can be compared with the previous traversal of the tree in order - it is a process from top to bottom and from left to right. Usually, in this process, the current element will no longer affect the elements that have been traversed before.
The most easily overlooked operation: get the values of some specific attributes
When you want to use attributes like offsetTop, offsetLeft, offsetWidth, offsetHeight, scrollTop, scrollLeft, scrollWidth, scrollHeight, clientop, clientLeft, clientWidth, clientHeight, you should pay attention!
What is the attribute of "like this"—— One thing these values have in common is that they need to be calculated in real time. Therefore, the browser will also reflow in order to obtain these values.
In addition, when we call getComputedStyle method or currentStyle in IE, backflow will also be triggered. The principle is the same, both for "immediacy" and "accuracy".
How to avoid backflow and redrawing
Knowing the "fuse" of reflow and redrawing, we should try to avoid them. But many times, we have to use them. When avoidance is unavoidable, we must learn to use them wisely.
Cache the "fuse" to avoid frequent changes
Sometimes we want to get the layout position of an element through multiple calculations. We may do this:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style> #el { width: 100px; height: 100px; background-color: yellow; position: absolute; } </style> </head> <body> <div id="el"></div> <script> // Get el element const el = document.getElementById('el') // It may expand the actual demand in a relatively simple judgment cycle for(let i=0;i<10;i++) { el.style.top = el.offsetTop + 10 + "px"; el.style.left = el.offsetLeft + 10 + "px"; } </script> </body> </html>
In this way, each cycle needs to obtain multiple "sensitive attributes", which is relatively bad. We can cache it in the form of JS variables and submit it to the browser for recalculation after calculation:
// Cache the values of offsetLeft and offsetTop const el = document.getElementById('el') let offLeft = el.offsetLeft, offTop = el.offsetTop // Calculation at JS level for(let i=0;i<10;i++) { offLeft += 10 offTop += 10 } // Apply the calculation results to DOM at one time el.style.left = offLeft + "px" el.style.top = offTop + "px"
Avoid changing styles one by one and use class names to merge styles
For example, we can put this simple code:
const container = document.getElementById('container') container.style.width = '100px' container.style.height = '200px' container.style.border = '10px solid red' container.style.color = 'red'
Optimize into a class blessed appearance
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style> .basic_style { width: 100px; height: 200px; border: 10px solid red; color: red; } </style> </head> <body> <div id="container"></div> <script> const container = document.getElementById('container') container.classList.add('basic_style') </script> </body> </html>
Each time the former operates alone, it triggers a rendering tree change, resulting in the corresponding backflow and redrawing process.
After merging, it means that we send all the changes at one time and solve them with one style request.
"Take DOM offline"
The reflow and redrawing mentioned above will occur on the premise that "the element is on the page". Once we set display: none to the element and "remove" it from the page, our subsequent operations will not trigger reflow and redrawing - this operation of "removing" the element is called DOM offline.
Take the code snippet above as an example:
const container = document.getElementById('container') container.style.width = '100px' container.style.height = '200px' container.style.border = '10px solid red' container.style.color = 'red' ...((many similar subsequent operations are omitted)
This is the case after offline:
let container = document.getElementById('container') container.style.display = 'none' container.style.width = '100px' container.style.height = '200px' container.style.border = '10px solid red' container.style.color = 'red' ...((many similar subsequent operations are omitted) container.style.display = 'block'
Some students will ask, take out an element and put it back, won't it also trigger an expensive reflux? That's true, but we took it down. No matter how many times I operate this element, the operation cost of each step will be very low. When we only need to do a few DOM operations, the advantages of DOM offline are not obvious. Once the operation is frequent, the cost of "removing" and "putting back" will be very worthwhile.
Flush queue: browsers are not that simple
Based on our current knowledge, it is not difficult to understand the above optimization operation. Now let me ask you a question:
let container = document.getElementById('container') container.style.width = '100px' container.style.height = '200px' container.style.border = '10px solid red' container.style.color = 'red'
How many times did the browser reflow or redraw in this code?
"width, height and border are geometric attributes that trigger a reflow; color only changes the appearance and triggers a redraw."—— If you think so immediately, it means that you are a good classmate and have carefully read the previous content. Now let's run this code and see what the browser says:
Here are the clips of "Layout" and "Paint" (this picture is obtained through the Performance panel of Chrome, which will be taught later). We see that the browser only performs a reflow and a redraw - which is different from what we think. Why?
Because modern browsers are smart. The browser itself knows that if every DOM operation is fed back with a reflow or redraw immediately, it will not be able to carry the performance. So it caches a flush queue by itself and inserts the reflow and redrawing tasks triggered by us. When there are more tasks in the queue, or reach a certain time interval, or "have to", it will take these tasks out of the queue at one go. Therefore, we can see that even if we make four DOM changes, only one Layout and one Paint are triggered.
We are particularly careful here when we have to. When we introduced the "fuse" of reflow, we mentioned that there is a kind of special attribute, which has strong "immediacy". When we access these attributes, the browser will queue the tasks in the flush queue in advance in order to obtain the most accurate attribute value at this moment - this is the so-called "last resort" moment. The specific attribute values have been introduced in the small module "the most easily ignored operation", which will not be repeated here.