Reference – Do you really understand reflux and redraw

The browser’s rendering process

From the image above, we can see that the browser renders as follows:

  1. Parse HTML, generate DOM tree, parse CSS, generate CSSOM tree
  2. Combine DOM Tree and CSSOM Tree to generate Render Tree
  3. Layout(backflow): According to the generated rendering tree, backflow (Layout), get the geometry information of nodes (position, size)
  4. Painting(repainting): Get absolute pixel information for nodes based on rendered tree and geometry information from backflow
  5. Display: Send the pixel to the GPU and Display it on the page.

The rendering process looks pretty simple, so let’s look at what we did at each step.

Generate render tree

To build the render tree, the browser does the following:

  1. Each visible node is traversed starting at the root of the DOM tree.
  2. For each visible node, find the corresponding rules in the CSSOM tree and apply them.
  3. The render tree is composed from each visible node and its corresponding style.

So in the first step, since we talked about traversing the visible nodes, we need to know what nodes are not visible. Invisible nodes include:

  • Nodes that do not render output, such as script, meta, link, etc.
  • Some nodes are hidden by CSS. Such as display: none. Note that nodes hidden with opacity and opacity will still be displayed in the rendering tree. Only nodes with display: None are not displayed in the rendering tree.

From the example above, we can see that the span tag style has a display: None, so it doesn’t end up on the render tree.

Note: The render tree contains only visible nodes

backflow

By constructing the render tree, we combine the visible DOM nodes with their corresponding styles, but we also need to calculate their exact position and size in the device viewport. This stage of calculation is called backflow.

To figure out the exact size and location of each object on the site, the browser traverses from the root of the render tree, which can be represented in the following example:

<! DOCTYPEhtml>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Critial Path: Hello world!</title>
  </head>
  <body>
    <div style="width: 50%">
      <div style="width: 50%">Hello world!</div>
    </div>
  </body>
</html>
Copy the code

You can see that the first div sets the display size of the node to 50% of the viewport width, and the second div sets its size to 50% of the parent node. In the backflow phase, we need to convert the viewport to the actual pixel value depending on the width of the viewport. (As shown below)

redraw

Finally, we know which nodes are visible, the style of the visible nodes and the specific geometric information (location, size) by constructing the render tree and the backflow phase, so we can convert each node of the render tree into an actual pixel on the screen. This phase is called the redraw node.

Now that we know how the browser renders, let’s talk about when backflow redraws occur.

When reflux redraw occurs

As we have already seen, the backflow phase mainly computs the location and geometry of nodes, so when the page layout and geometry information changes, backflow is needed. For example:

  1. Add or remove visible DOM elements
  2. The position of the element changes
  3. The size of the element changes (including margins, inner borders, border size, height, width, etc.)
  4. Content changes, such as text changes or an image being replaced by another image of a different size.
  5. When the page is first rendered (which is inevitable)
  6. Browser window size changes (because backflow calculates element position and size based on viewport size)

Note: Backflow must trigger redraw, and redraw does not necessarily backflow

Depending on the extent and extent of the change, large or small portions of the rendering tree need to be recalculated. Some changes can trigger a rearrangement of the entire page, such as when a scrollbar appears or when a root node is modified.

Browser optimization mechanisms

Modern browsers are smart enough to optimize the reordering process by queuing changes and executing them in batches, since each reordering incurs an additional computation cost. The browser queues changes until a certain amount of time has passed or a threshold has been reached. But! Queue refresh is forced when you retrieve layout information from operations, such as when you access the following properties or use the following methods:

  • OffsetTop, offsetLeft, offsetWidth, offsetHeight
  • ScrollTop, scrollLeft, scrollWidth, scrollHeight
  • ClientTop, clientLeft, clientWidth, clientHeight
  • getComputedStyle()
  • getBoundingClientRect

Specific can access the site: gist.github.com/paulirish/5… All of the above attributes and methods need to return the latest layout information, so the browser has to clear the queue and trigger a backflow redraw to return the correct value. Therefore, it is best to avoid using the attributes listed above when modifying styles, as they all refresh the render queue. If you want to use them, it is best to cache the values.

Reduce backflow and redraw

Minimize redraw and rearrange

Since redrawing and rearranging can be expensive, it is best to reduce the number of times it occurs. To reduce the number of occurrences, we can merge the DOM and style changes multiple times and then dispose of them all at once. Consider this example

const el = document.getElementById('test');
el.style.padding = '5px';
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
Copy the code

In the example, there are three style attributes that have been modified, each of which affects the element geometry and causes backflow. Of course, most modern browsers are optimized for this, so that only one rearrangement is triggered. However, assuming that other code accessed the layout information (the one that triggered backflow above) on an older browser or while the above code was executing, this would result in three rearrangements.

Therefore, we can merge all the changes and deal with them in sequence. For example, we can do the following:

  • Using cssText
const el = document.getElementById('test');
el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px; ';
Copy the code
  • Modify the CSS class
const el = document.getElementById('test');
el.className += ' active';
Copy the code

Batch modify DOM

When we need to make a series of DOM changes, we can reduce the number of backflow redraws by following these steps:

  • Takes the element out of the document flow
  • Modify it multiple times
  • Bring the element back into the document.

Steps 1 and 3 of the process may cause backflow, but after step 1, any changes to the DOM will not cause backflow redraw because it is no longer in the render tree.

There are three ways to take the DOM out of document flow:

  • Hide elements, apply changes, and redisplay
  • Use document fragments to build a subtree outside the current DOM and copy it back into the document.
  • Copy the original element to a node out of the document, modify the node, and replace the original element.

Consider that we want to execute a chunk of code to insert nodes in batches:

function appendDataToElement(appendToElement, data) {
    let li;
    for (let i = 0; i < data.length; i++) {
    	li = document.createElement('li');
        li.textContent = 'text'; appendToElement.appendChild(li); }}const ul = document.getElementById('list');
appendDataToElement(ul, data);
Copy the code

If we did this directly, we would cause the browser to flow back once, since each loop would insert a new node.

There are three ways to optimize:

Hide elements, apply changes, and redisplay

This will result in two backflows when the node is shown and hidden

function appendDataToElement(appendToElement, data) {
    let li;
    for (let i = 0; i < data.length; i++) {
    	li = document.createElement('li');
        li.textContent = 'text'; appendToElement.appendChild(li); }}const ul = document.getElementById('list');
ul.style.display = 'none';
appendDataToElement(ul, data);
ul.style.display = 'block';
Copy the code

Use document fragments to build a subtree outside the current DOM and copy it back into the document

const ul = document.getElementById('list');
const fragment = document.createDocumentFragment();
appendDataToElement(fragment, data);
ul.appendChild(fragment);
Copy the code

Copy the original element to a node out of the document, modify the node, and replace the original element.

const ul = document.getElementById('list');
const clone = ul.cloneNode(true);
appendDataToElement(clone, data);
ul.parentNode.replaceChild(clone, ul);
Copy the code

Modern browsers use queues to store multiple changes and optimize, so this optimization is not really a priority.

Avoid triggering synchronous layout events

As we mentioned earlier, when we access some attributes of an element, the browser forces the queue to be emptied and the layout to be synchronized. For example, if we wanted to assign the width of an array of P tags to the width of an element, we might write code like this:

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

This code looks fine, but it can cause a lot of performance problems. Each time through the loop, an offsetWidth property value of the box is read and used to update the width property of the P tag. This results in the browser having to validate the style update in the previous loop before it can respond to the style read in the current loop. Each loop forces the browser to refresh the queue. We can optimize as:

const width = box.offsetWidth;
function initP() {
    for (let i = 0; i < paragraphs.length; i++) {
        paragraphs[i].style.width = width + 'px'; }}Copy the code

For complex animation effects, use absolute positioning to keep them out of the document flow

For complex animation effects, which often cause backflow redraw, we can use absolute positioning to take it out of the document stream. Otherwise, it will cause frequent backflow of the parent element and subsequent elements.

Css3 Hardware acceleration (GPU acceleration)

Rather than thinking about how to reduce backflow redraw, we’d like to see no backflow redraw at all. This time, CSS3 hardware acceleration on the shining debut!!

To highlight:

  1. Using CSS3 hardware acceleration, transform, opacity, and filters can be rendered without backflow redrawing.

  2. Other properties of animations, such as background-color, will still cause backflow redraw, but it can still improve the performance of those animations.

How to use

Common CSS properties that trigger hardware acceleration:

  • transform
  • opacity
  • filters
  • Will-change

Focus on

  • Using CSS3 hardware acceleration, transform, opacity, and filters can be rendered without backflow redrawing
  • Other properties of animations, such as background-color, will still cause backflow redraw, but it can still improve the performance of those animations.

Css3 hardware acceleration pit

Of course, any good thing has its price, too much of a good thing. Css3 hardware acceleration is still a pit:

  • If you use CSS3 hardware acceleration for too many elements, it can lead to a large memory footprint and performance issues.
  • Anti-aliasing will not work on GPU rendering fonts. This is because GPU and CPU algorithms are different. So if you don’t turn hardware acceleration off at the end of the animation, the font will blur.