From the time I touched the front end until now, I’ve been hearing one thing: DOM manipulation is expensive, so don’t manipulate DOM lightly. Especially with the emergence of MV* frameworks like React and Vue, the mode of data-driven view is getting more and more popular. The powerful and convenient API for manipulating DOM provided by jQuery era is used less and less in front-end engineering. After all, what is the high cost here?
What is the DOM
Document Object Model Document Object Model
What is DOM? Most people’s first reaction is to div, P, span and other HTML tags (at least I am), but remember that DOM is the Model, the Object Model, and the API for HTML (and XML). HTML(Hyper Text Markup Language) is a Markup Language. HTML is regarded as an object in the DOM model standard. DOM only provides programming interface, but cannot actually operate the content in HTML. On the browser side, the front-end can manipulate HTML content through the DOM using JavaScript.
So the question is, can only JavaScript call the DOM API?
The answer is NO.
Python can also access the DOM. So DOM is not an API for Javascript, nor is it an API in Javascript.
PS: In essence, there is also the CSSOM: CSS Object Model. Browsers parse CSS code into tree data structures, which are two independent data structures from DOM.
Browser rendering process
To discuss DOM manipulation costs, you must first understand the source of those costs, and browser rendering is essential.
We’re just talking about the browser taking the HTML and parsing it and rendering it. (How to get HTML resources may follow another opening summary, such as shake hands, wave hands, evil flag…)
-
Parsing the HTML, building the DOM tree (where the external chain is encountered, the request will be made)
-
Parses the CSS and generates the CSS rule tree
-
Combine DOM tree and CSS rules to generate render tree
-
Render tree (Layout/reflow), responsible for each element size, location calculation
-
Render the Render tree (paint), which draws the page pixel information
-
The browser sends each layer of information to the GPU, which composes the layers and displays them on the screen
1. Build a DOM tree
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet"> <title>Critical Path</title> </head> <body> <p>Hello <span>web performance</span> students! </p> <div><img src="awesome-photo.jpg"></div>
</body>
</html>
Copy the code
Both DOM and CSSOM need to go through the process of Bytes → characters → Tokens → Nodes → Object Model.
DOM tree building process: The next sibling of the current node is built only after all the children of the current node are built.
2. Build the CSSOM tree
As mentioned above, the CSSOM construction process is also a tree structure. In the final calculation of the style of each node, the browser will start from the general properties of the node (such as the global style set in the body), and then apply the specific properties of the node. Also note that each browser has its own default style sheet, so many times this CSSOM tree is just a partial replacement for that default style sheet.
3. Generate render tree
The DOM tree and the CSSOM tree are combined to produce the Render tree
Briefly describe the process:
The DOM tree traverses visible nodes from the root node. The “visible” is emphasized here because if you encounter a setting like display: None; Undefined nodes will be skipped during render (but visibility: hidden; Opacity: 0; function: none; function: none; function: none; function: none; function: none
4. The Layout arrangement
You have style information and attributes for each node, but you don’t know the exact location and size of each node, so you use layout to translate style information and attributes into the relative size and position of the actual visual window.
5. Paint Paint
Everything is ready, and all you need to do is to render each node with the right location and size to the actual pixels on the screen via the GPU.
Tips
- In the above rendering process, the first 3 points may be executed several times, such as JS script to manipulate the DOM, change the CSS style, the browser has to rebuild the DOM, CSSOM tree, re-render, re-layout, paint;
- Layout comes before Paint, so every time a Layout is reflow, you have to start painting again, which consumes the GPU.
- Paint does not necessarily trigger a Layout, such as changing a color or background. (Repaint)
- Layout and Paint will start again after images are downloaded.
When reflow and Repaint are triggered
Reflow: According to the Render Tree layout (geometry attributes), meaning that the content, structure, position, or size of the element has changed, requiring recalculation of styles and Render trees; Repaint: Means that the changes to the element only affect some of the node’s styles (background color, border color, text color, etc.), just apply the new style to the element. Reflow is more expensive than repaint, and a node’s reflow often results in a child node’s reflow and its sibling’s reflow.
There are csstriggers in GoogleChromeLabs that list the effects of each CSS property on how the browser executes Layout, Paint, and Composite.
Causes reflow backflow
Modern browsers are optimized for backflow by waiting until a sufficient number of changes have occurred to do another batch backflow.
- First rendering of the page (initialization)
- DOM tree changes (e.g., adding and deleting nodes)
- Render tree changes (e.g., padding changes)
- Resize the browser window
- Gets some attributes of the element: Browsers also trigger backflow in advance to get the right value, which invalidates browser optimizations, These attributes include offsetLeft, offsetTop, offsetWidth, offsetHeight, ScrollTop/Left/Width/Height, clientTop/Left/Width/Height, calls the getComputedStyle currentStyle () or Internet explorer
Causes repaint to redraw
- Reflow reflow must cause repaint to be repainted, and repaint can be triggered separately
- Background color, color, font changes (note: Backflow is triggered when font size changes)
Optimize reflow and Repaint trigger times
- Avoid changing node styles one by one and try to change them all at once
- Use the DocumentFragment to cache DOM elements that need to be modified several times and append them to the real DOM once for rendering
- You can set up DOM elements that need to be modified multiple times
display: none
, and then display after operation. (Because hidden elements are not in the Render tree, modifying hidden elements does not trigger backflow redraw) - Avoid reading certain properties more than once (see above)
- Take complex node elements out of the document flow, reducing backflow costs
Why the emphasis on CSS at the head and JS at the tail
DOMContentLoaded and load
- The DOMContentLoaded event is triggered only when the DOM is loaded, not including stylesheets, images…
- When the load event is triggered, all the DOM, stylesheets, scripts, and images on the page have been loaded
CSS resources block rendering
Building the Render tree requires DOM and CSSOM, so both HTML and CSS block rendering. So you need to load CSS as early as possible (e.g. in the header) to reduce the first rendering time.
JS resources
- Block browser parsing, which means that if an external script is found, parsing HTML will continue until the script has been downloaded and executed
- Javascript engine threads and rendering threads are mutually exclusive. See settimeout-setInterval for more information on javascript threads.
- Normal scripts block browser parsing, and with defer or async properties, the script becomes asynchronous and can wait until the parsing is complete
- Async: async is executed asynchronously, after the async download is completed, the execution order is not sure, must be before the onload, but not sure before or after the DOMContentLoaded event
- Defer, as opposed to putting it at the end of the body (theoretically before the DOMContentLoaded event)
Take a chestnut
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet"> </head> <body> <p>Hello <span>web performance</span> students! </p> <div><img src="awesome-photo.jpg"></div>
<script src="app.js"></script>
</body>
</html>
Copy the code
- The browser takes the HTML and parses the document from top to bottom
- When CSS and JS chains are encountered, the request is initiated at the same time
- Start building the DOM tree
- Note here that CSSOM blocks JS (if any) before it is built due to CSS resources
- It doesn’t matter whether JavaScript is inlined or outlinked, as long as the browser encounters it
script
Mark, wake upJavaScript parser
, will be pausedblocked
The browser parses the HTML and waitsCSSOM
Execute the JS script only after the construction is complete - Render the first screen (DOMContentLoaded)
First screen optimization Tips
Having said that, we can actually summarize a few directions for optimizing the browser’s first screen rendering
- Reduce the number of resource requests (inline or delayed dynamic loading)
- Make CSS stylesheets load as early as possible and reduce the use of @import because CSS resources are not downloaded until all the imported resources in the stylesheet have been parsed
- Asynchronous JS: JavaScript that blocks the parser forces the browser to wait for CSSOM and pauses DOM construction, resulting in a delay in first rendering
- so on…
See how expensive it is to manipulate the DOM?
In fact, write so much, feel off topic, a large number of references are chrome developer documentation. Feel js script resources that block is still a bit messy, including the relationship with DOMContentLoaded, I hope you can give more advice, more criticism, thank you big brothers.
The actual cost of manipulating the DOM is essentially causing the browser to reflow and redraw reflow, thus consuming GPU resources.
References:
Developers.google.com/web/fundame…
Synced to personal blog – hard or soft
Github welcome star 🙂