In the introduction, I recently spent some fragmented time reading Another of Nicholas’ great books, High Performance JavaScript, and today’s article starts with the “cliche” of page redrawing and rearranging to explore the role of these two concepts in improving page performance.
I. Rearrangement & redrawing
Experienced executives will be familiar with the concept of “what happens when a browser enters a URL”. It is estimated that we are already familiar with the heart, from computer network to JS engine, all the way to the browser rendering engine. The more experience you have, the more you understand. For those of you who are interested in this article, the depth and the breadth of the process from entering the URL to loading the page, right? How to improve their front-end knowledge system by a problem!
Let’s get back to what rearrangement is. Once the browser has downloaded all of the page’s resources, it starts to build the DOM Tree, along with the Render Tree. The Style Tree is actually built at the same time as the DOM Tree before building the render Tree. DOM Tree and Style Tree merged into render Tree)
- The DOM tree represents the structure of the page
- How are the nodes of the render tree representing the page displayed
Once the render tree is built, it’s time to paint the page elements. When a DOM change causes an element’s geometry to change, such as its width, height, or position, the browser has to recalculate the element’s geometry and rebuild the render tree, a process known as “rearrangement.” After the rearrangement is complete, the reconstructed render tree is rendered to the screen, a process known as “redraw”. Simply put, rearrangement is responsible for updating elements’ geometry and redrawing is responsible for updating elements’ styles. Moreover, rearrangement necessarily leads to redrawing, but redrawing does not necessarily lead to rearrangement. For example, if you change the background of an element, it doesn’t involve the element’s geometry, so it just redraws.
Ii. Rearrangement trigger mechanism
As mentioned above, the fundamental principle of rearrangement is that the geometric properties of elements are changed, so let’s start from the Angle that can change the geometric properties of elements
- Add or remove visible DOM elements
- Element position change
- The size of the element itself changes
- Content change
- The page renderer is initialized
- The browser window size changed. Procedure
Iii. How to optimize performance
The overhead of redrawing and rearranging is very expensive. If we keep changing the layout of the page, it will cause the browser to spend a lot of overhead in the calculation of the page. In this case, our page will appear obvious lag when the user uses it. Today’s browsers have been optimized for rearrangement, as shown in the following code:
var div = document.querySelector('.div');
div.style.width = '200px';
div.style.background = 'red';
div.style.height = '300px';
Copy the code
More older browsers, this code will trigger the rearrangement of page 2 times that in, respectively, set high, wide trigger twice, contemporary browsers are optimized, this approach is similar to the now popular MVVM framework USES virtual DOM, the change of the DOM nodes rely on collection, confirm the node has not changed, just for an update. However, browser optimization for rearrangement is similar to the virtual DOM, but there are essential differences. Most browsers optimize the reordering process by queuing changes and executing them in batches. That is to say, the above code is actually a rearrangement under the current browser optimization. However, there are some special element geometry properties that can cause this optimization to fail. Such as:
- OffsetTop, offsetLeft,…
- scrollTop, scrollLeft, …
- clientTop, clientLeft, …
- getComputedStyle() (currentStyle in IE)
Why do optimizations fail? If you look closely at these attributes, they are geometry or layout attributes that need to be fed back to the user in real time, and of course can no longer rely on browser optimization, so the browser has to perform “pending changes” in the render queue immediately and trigger reordering to return the correct value. Let’s dive into a few performance tuning TIPS
3.1 Minimize redraw and rearrangement
Since rearranging and redrawing can affect page performance, poor JS code in particular can amplify the performance problems caused by rearranging. In this case, our first thought was to reduce rearrangement and redraw.
3.1.1. Change styles
Consider the following example:
// javascript
var el = document.querySelector('.el');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
Copy the code
This example is actually the same as the above example, and in the worst case, will trigger the browser to rearrange three times. However, a more efficient way is to combine all changes at once. This will only modify the DOM node once, such as using the cssText property instead:
var el = document.querySelector('.el');
el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px';
Copy the code
Along this line of thought, the wise old iron must say, you directly change a class name is not no problem. Yes, another way to reduce rearrangement is to switch class names instead of using the inline cssText method. Using the toggle class name looks like this:
// css
.active {
padding: 5px;
border-left: 1px;
border-right: 2px;
}
// javascript
var el = document.querySelector('.el');
el.className = 'active';
Copy the code
3.1.2 Modifying DOM in Batches
If we need to make multiple changes to DOM elements, how can we reduce the number of rearrangements and redraws? Some students want to say again, use the above method to modify the style is not on the line. Looking back at the points that caused the page rearrangement, we can clearly see that the rearrangement will be triggered by the change of the element geometry attribute. Now we need to add 10 nodes, which inevitably involves the modification of DOM. At this time, we need to use the optimization method of batch modification of DOM, which can also be seen here. Changing styles minimizes redraw and rearrangement this optimization applies to a single existing node. The core idea behind batch modification of DOM elements is:
- Take the element out of the document flow
- Make multiple changes to it
- Bring the element back into the document
For example, if the hard disk of our host fails, the common way is to remove the hard disk, test where there is a problem with professional tools, and then install it after repair. If directly on the motherboard with a screwdriver to get to get to the motherboard, it is estimated that the motherboard will be broken for a while…
This process causes two rearrangements, step 1 and step 3. Without those two steps, you can imagine that step 2 causes a rearrangement for every DOM addition and deletion. Now that we know the core idea of batching the DOM, we’ve looked at three more ways to keep elements out of the document stream. Note that we don’t use CSS floats & absolute positions, which are completely unrelated concepts.
- Hide the element, modify it, and then display it
- Use the document fragment to create a subtree, and then copy it into the document
- Copy the original element into a separate node, manipulate the node, and then overwrite the original element
Take a look at the following code example:
// html <ul id="mylist"> <li><a href="https://www.mi.com">xiaomi</a></li> <li><a Href ="https://www.miui.com">miui</a></li> </ul> // javascript 'https://www.baidu.com', }, { name: 'ann', url: 'https://www.techFE.com' } ]Copy the code
First, we’ll write a generic method for updating new data to a specified node:
// javascript function appendNode($node, data) { var a, li; for(let i = 0, max = data.length; i < max; i++) { a = document.createElement('a'); li = document.createElement('li'); a.href = data[i].url; a.appendChild(document.createTextNode(data[i].name)); li.appendChild(a); $node.appendChild(li); }}Copy the code
First of all, we ignore all the rearrangement factors, and you’re going to write:
let ul = document.querySelector('#mylist');
appendNode(ul, data);
Copy the code
With this approach, each insertion of a new node causes a rearrangement without any optimization (we discuss rearrangement first in these sections, because rearrangement is the first step in performance optimization). Consider this scenario, if we add a large number of nodes, and the layout is complex, and the style is complex, then you can imagine that your page must be very sluggish. We use optimization techniques that modify the DOM in bulk for refactoring
1) Hide the element, modify it, and then display it
let ul = document.querySelector('#mylist');
ul.style.display = 'none';
appendNode(ul, data);
ul.style.display = 'block';
Copy the code
This approach results in two rearrangements that control the display and hiding of elements. Consider this approach for complex, large number of node paragraphs. Opacity :none, opacity: 0, opacity: hidden: opacity: 0, opacity: hidden: opacity: 0, opacity: hidden: opacity: 0, opacity: hidden: opacity: 0, opacity: hidden: opacity: 0
2) Create a subtree using the document fragment and then copy it into the document
let fragment = document.createDocumentFragment();
appendNode(fragment, data);
ul.appendChild(fragment);
Copy the code
I am prefer this method, document fragments is a lightweight document object, it is used to update the purpose of the design, such as mobile node task, and document fragments there is one advantage is that, when added to a node document fragments, add the document fragments of child nodes, itself will not be added. Unlike the first method, this method does not cause logic problems by making the element disappear temporarily. In the example above, only one rearrangement is involved when adding document fragments.
3) Copy the original element into a separate node, manipulate the node, and then overwrite the original element
let old = document.querySelector('#mylist');
let clone = old.cloneNode(true);
appendNode(clone, data);
old.parentNode.replaceChild(clone, old);
Copy the code
You can see that this method also only has one rearrangement. In general, using document fragments allows you to manipulate less DOM (as opposed to using cloned nodes) and minimize rearranging and redrawing times.
3.1.3 Caching Layout Information
Cache layout information this concept, in the high-performance JavaScript DOM performance optimization, repeatedly mentioned similar ideas, such as I have to get the page below 100 li ul node, the best thing to do is get saved after the first time, reducing the access of the DOM to improve performance, cache layout information is also the same idea. As mentioned earlier, when accessing properties such as offsetLeft and clientTop, the browser’s own optimizations ———— reduce the number of rearranges/redraws through queued modifications and batch runs. Therefore, we should minimize the number of queries for layout information. When querying, we should assign it to local variables and use local variables to participate in the calculation. Look at this example: Pan the element div down and right 1px at a time, starting at 100px, 100px. Poor performance code:
div.style.left = 1 + div.offsetLeft + 'px';
div.style.top = 1 + div.offsetTop + 'px';
Copy the code
The problem is that the div offsetLeft is called every time, causing the browser to force a refresh of the render queue to get the latest offsetLeft value. A better idea is to save the value and avoid repeating the value
current = div.offsetLeft;
div.style.left = 1 + ++current + 'px';
div.style.top = 1 + ++current + 'px';
Copy the code