preface

This article is a learning record of the blogger’s recent efforts to deal with some of the site’s problems. Front-end best practices (a) — DOM manipulation, if you feel useful can give a star, thank you. Author: wengjq

1. Browser rendering principle

Before looking at best performance practices for DOM manipulation, let’s take a look at the basic rendering principles of the browser. The main process of browser rendering and displaying web pages can be roughly represented as follows:

Figure: WebKit main flow

It is divided into the following four steps:

  • (HTML parsing HTML Parser)

  • Building a DOM Tree

  • Render Tree Construction

  • Render tree (Painting)

The browser parses the HTML document and converts each tag into a DOM Tree. It also parses style data in external CSS files and style elements. This style information in HTML with visual instructions will be used to create another Tree structure: the Render Tree. The Render Tree contains multiple rectangles with visual attributes such as color and size. The order in which these rectangles are arranged is the order in which they will appear on the screen. Once the Render Tree is built, the “layout” process is completed, which assigns each node an exact coordinate that should appear on the screen. The next stage is Painting – the browser iterates over the Render Tree, drawing each node from the user interface back-end layer.

It should be emphasized that this is a gradual process. For a better user experience, browsers strive to get content on screen as quickly as possible. It doesn’t have to wait until the entire HTML document has been parsed to start building the rendering tree and setting up the layout. While the rest of the content from the web is being received and processed, the browser parses and displays some of it.

2, Repaints and reflows

Repaint: Can be interpreted as repainting or repainting, when some elements in the Render Tree need to be updated with properties that affect the appearance, style, and layout of the elements, such as changing the background color. It’s called redrawing. Reflows: Can be understood as Reflows, layouts, or rearrangements, when part (or all) of the Render Tree needs to be rebuilt due to changes in element size, layout, hiding, etc. This is called reflow, or relayout.

When is backflow or redraw triggered?

Changing anything used to build the render tree may cause redrawing or redrawing, such as adding, deleting, or updating DOM nodes. Using display: None (redrawing and redrawing) or visibility: Add style sheet, adjust style properties 4. Resize window, change font size 5. Render page initialization 6. Move DOM element…

Let’s look at a few examples:

var bstyle = document.body.style; // cache

bstyle.padding = "20px"; // reflow, repaint
bstyle.border = "10px solid red"; // another reflow and a repaint

bstyle.color = "blue"; // repaint only, no dimensions changed
bstyle.backgroundColor = "#fad"; // repaint

bstyle.fontSize = "2em"; // reflow, repaint

// new DOM element - reflow, repaint
document.body.appendChild(document.createTextNode('dude! '));Copy the code

We can imagine adding or removing a node directly at the end of the render Tree. This is not a big deal for the browser to render the page because it only needs to redraw the changed nodes at the end of the render Tree. However, if a node is changed at the top of the page, the browser needs to recalculate the Render Tree, causing part or all of the Tree to change. After the Render Tree is rebuilt, the browser redraws the affected elements on the page. The cost of rearrangement is much higher than the cost of painting, redrawing will affect some elements, and rearrangement may affect all elements.

3. Best practices for DOM manipulation

Page Repaints and Reflows brought about by DOM manipulation are inevitable, but there are some best practices that can be followed to minimize Repaints and Reflows. Here are some specific practices:

3.1 Merge multiple DOM operations

// bad
var left = 10,
  top = 10;
el.style.left = left + "px";
el.style.top  = top  + "px";

// better 
el.className += " theclassname";
// better
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";Copy the code

Since Repaints and Reflows associated with rendering tree changes are very costly, modern browsers have optimized performance for frequent Repaints and Reflows. One strategy is that the browser will set up the queue of changes required by the script and execute them in batches. In this way, several changes for each required Reflows will be combined, and only one Reflows will be counted. Browsers can add queued changes and then refresh the queue after a certain amount of time has passed or a certain number of changes have been made (not all browsers have this optimization. The recommended approach is to merge DOM operations as much as possible). But sometimes the script may prevent the browser from optimizing Reflows and cause it to refresh the queue and perform all batch changes. This happens when you request the following style information (not all of it). See below:

All of this is basically a request for style information about the node, and the browser must provide the latest values. To do this, it needs to apply all planned changes, flush the queue, and force backflow. Therefore, when a large number of DOM operations are performed, it is necessary to avoid obtaining the layout information of DOM elements, so that the browser optimization for large number of DOM operations is not damaged. If you need this layout information, it’s best to get it before DOM manipulation.

//bad
var bstyle = document.body.style;

bodystyle.color = 'red';
tmp = computed.backgroundColor;

bodystyle.color = 'white';
tmp = computed.backgroundImage;

bodystyle.color = 'green';
tmp = computed.backgroundAttachment;

//better
tmp = computed.backgroundColor;
tmp = computed.backgroundImage;
tmp = computed.backgroundAttachment;

bodystyle.color = 'yellow';
bodystyle.color = 'pink';
bodystyle.color = 'blue';Copy the code

Make DOM elements out of the Render Tree

(1) use DocumentFragments as DOM nodes. They are not part of the main DOM tree. The usual use case is to create a document fragment, attach elements to the document fragment, and then attach the document fragment to the DOM tree. In the DOM tree, the document fragment is replaced by all of its children. Because the document fragment exists in memory, not in the DOM tree, inserting child elements into the document fragment does not cause page Reflow. Of course, the last step of attaching the document fragment to the page still causes Reflow.

var fragment = document.createDocumentFragment(); // A bunch of DOM operations based on fragments... document.getElementById('myElement').appendChild(fragment);Copy the code

(2) Hide elements by setting the display style of DOM elements to None. The principle is to hide elements first, then perform DOM operations on the elements, and then display the elements after a lot of DOM operations.

var myElement = document.getElementById('myElement');
myElement.style.display = 'none'; // Some heavy DOM manipulation based on myElement... myElement.style.display ='block';Copy the code

(3) Clone DOM elements into memory this way is to clone a DOM element on the page into memory, and then operate the cloned element in memory, after the operation is completed, use the cloned element to replace the original DOM element in the page.

var old = document.getElementById('myElement');
var clone = old.cloneNode(true); // Some based oncloneA lot of DOM manipulation... old.parentNode.replaceChild(clone, old);Copy the code

3.3. Cache style information using local variables

Getting style information for the DOM has a performance penalty, so if there are looping calls, it’s best to try to cache those values in local variables.

// bad
function resizeAllParagraphsToMatchBlockWidth() {
    for (var i = 0; i < paragraphs.length; i++) {
        paragraphs[i].style.width = box.offsetWidth + 'px';
    }
}

// better
var width = box.offsetWidth;
function resizeAllParagraphsToMatchBlockWidth() {
    for (var i = 0; i < paragraphs.length; i++) {
        paragraphs[i].style.width = width + 'px'; }}Copy the code

3.4. Set animated DOM elements to fixed positions

Using absolute positioning makes the element a direct child node under the body in the rendering tree, so it doesn’t affect too many other nodes when it’s animated.

4. Specific examples

4.1 browser batch processing and backflow

This is illustrated by a specific example, linked to reflow.

The code for the first click is as follows:

function touch() {
    bodystyle.color = 'red';
    bodystyle.padding = '1px';
    tmp = computed.backgroundColor;
    bodystyle.color = 'white';
    bodystyle.padding = '2px';
    tmp = computed.backgroundImage;
    bodystyle.color = 'green';
    bodystyle.padding = '3px';
    tmp = computed.backgroundAttachment;
}Copy the code

The code for the second click is as follows:

function touchlast() {
    tmp = computed.backgroundColor;
    tmp = computed.backgroundImage;
    tmp = computed.backgroundAttachment;
    bodystyle.color = 'yellow';
    bodystyle.padding = '4px';
    bodystyle.color = 'pink';
    bodystyle.padding = '5px';
    bodystyle.color = 'blue';
    bodystyle.padding = '6px';
}Copy the code

Here’s a look at the similarities and differences between the two operations using Google tools.

4.1.1. First open the above link with Google Chrome. Press F12 to switch to the Performance option

The results are shown below:

4.1.2. Press CTRL + E (or click the dot) to start recording, click the Body area, and click “Stop” to stop recording after the text turns green

The results are shown below:

4.1.3 Select the part with sudden rise in blue (JS heap) in the figure above to indicate the process of clicking body just now, and scroll the mouse to enlarge the main thread

The result is shown below, note where the arrow points:

From the figure above we can easily see that the browser calculates the style three times before clicking on the body.

4.1.4. Click the clear button next to the dot to clear it and repeat the above operations until the text turns blue and stops:

The result is shown below, note where the arrow points:

From the figure above, you can easily see that the browser only computed the style once before clicking on the body again. Thus we can prove the conclusion of browser batch processing above. The unoptimized Rendering time is 0.4ms, while the optimized Rendering time is 0.3ms, which makes such a big difference in such a small JS execution. In some animations that manipulate the DOM frequently, the wasted Rendering time can be expected, which will be shown in an animation example below.

4.2. Impact of frequent backflow

The example given by Google Docs is linked to: animation.

Code before optimization:

var pos = m.classList.contains('down')? m.offsetTop + distance : m.offsetTop - distance;if (pos < 0) pos = 0;
    if (pos > maxHeight) pos = maxHeight;
        m.style.top = pos + 'px';
    if (m.offsetTop === 0) {
        m.classList.remove('up');
        m.classList.add('down');
    }
    if (m.offsetTop === maxHeight) {
        m.classList.remove('down');
        m.classList.add('up');
    }Copy the code

Optimized code:

var pos = parseInt(m.style.top.slice(0, m.style.top.indexOf('px')));
    m.classList.contains('down')? pos += distance : pos -= distance;if (pos < 0) pos = 0;
    if (pos > maxHeight) pos = maxHeight;
    m.style.top = pos + 'px';
    if (pos === 0) {
        m.classList.remove('up');
        m.classList.add('down');
    }
    if (pos === maxHeight) {
        m.classList.remove('down');
        m.classList.add('up');
    }Copy the code

Throttling the CPU, then adding the “Google” icon until the icon slowed down significantly, then clicking the “Optimize” button will show the difference. How to throttle the CPU and locate the problem can refer to my other article what? Page lag? Operating slow? .