Those of you who have done front-end development have heard of rearrangement and redraw, even if you don’t understand them very well. So why are these two things so important? Today, we’re going to take a closer look at both of these things because they are so important to our page performance.

The browser’s rendering flow

Before we get into rearranging and redrawing, it’s worth talking about the browser rendering process. Here are some of the most critical parts of the browser rendering process. If you want to know the complete browser rendering process, we recommend you to read Teacher Li Bing’s browser working principle practice, you need to pay to read. Or take a look at this blog post that gives you a thorough understanding of the browser rendering process

  • JavaScript: In general, we use JavaScript for visual changes. Do an animation, sort a data set, or add DOM elements to a page using jQuery’s Animate function. Of course, in addition to JavaScript, there are other common ways to achieve visual changes, such as CSS Animations, Transitions, and the Web Animation API.
  • Style calculation: This is the process of figuring out which elements apply which CSS rules based on the match selector (for example.headline or.nav >.nav__item). Once you know the rules from this, you apply the rules and calculate the final style for each element.
  • Layout: Once you know which rules to apply to an element, the browser can start calculating how much space it takes up and where it sits on the screen. The layout pattern of a web page means that one element can affect other elements, for example, the width of an element generally affects the width of its children and nodes throughout the tree, so the layout process is a common occurrence for browsers.
  • Drawing: Drawing is the process of filling pixels. It involves drawing text, colors, images, borders, and shadows, basically including every visible part of an element. Drawing is typically done on multiple surfaces (often called layers).
  • Composition: Because parts of a page may be drawn to multiple layers, they need to be drawn to the screen in the correct order to render the page correctly. This is especially important for elements that overlap with another element, because an error can cause one element to mistakenly appear on top of another.

Among them, rearrangement and redrawing affect the layout and drawing process.

What are rearrangements and redraws

  • Rearrangement: When a DOM change causes an element’s geometry to change, such as its width, height, or position, causing the browser to recalculate the element’s geometry and rebuild the render tree, this process is called “rearrangement.”
  • Redraw: After the rearrangement is complete, the reconstructed render tree is rendered onto the screen, a process known as “redraw.”

Simply put, when it comes to geometric updates of elements, it’s called rearrangement. When only style updates are involved but not geometry updates, it is called redraw. In both cases, rearrangement always leads to redrawing, but redrawing does not necessarily lead to rearrangement. So, when it comes to reordering, the browser will do it all over again. When only redraw is involved, the browser skips the Layout step, that is:

What operations cause rearrangements and redraws

It is obvious that the rearrangement is triggered by geometric factors in general, and this is easier to understand:

  • Page First rendering The most expensive rearrangement occurs when a page is first rendered, and all components are laid out for the first time
  • Browser window size changed
  • When the position and size of an element change
  • Add and remove visible elements
  • Content changes (number of words or image size, etc.)
  • Element font size changes

There are other actions that can cause rearrangements

  • Query some properties or call some methods
    • offset(Top|Left|Width|Height)
    • scroll(Top|Left|Width|Height)
    • client(Top|Left|Width|Height)
    • getComputedStyle()

We may not understand why these operations can also cause rearrangements, but let me explain briefly here. Because today’s browsers are so polished that they automatically do some optimizations for us. When we manipulate the DOM with JS, the browser doesn’t do it right away, but stores it in a queue. When a certain amount of time or time has passed, the browser uniformly executes the queue operation. So going back to our question, why does querying these attributes also cause reordering? Because when you query for these properties, the browser forces the queue to refresh, because if you don’t do something in the queue right away, you might get the wrong result. So you force a break in the browser optimization process, causing a rearrangement. Let’s take a look at some examples to understand the passage:

First let’s do an operation that obviously causes a rearrangement


      
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
  <style>
    #test {
      width: 100px;
      height: 100px;
      background-color: red;
      position: relative;
    }
  </style>
</head>
<body>
  <div id="test">

  </div>
  <button onclick="reflow()">click</button>
  <script>
    function reflow() {
      var div = document.querySelector("#test");
      div.style.left = '200px';
    }
  </script>
</body>
</html>
Copy the code

When we click the button, we set the left of the div to 200px, which obviously causes the browser to rearrange. Let’s use Chrome’s performence tool to analyze the left shift process.

  • Recalculate Style: This is the process that generates the CSSOM
  • Layout: This is the Layout stage, the process of rearranging
  • Update Layer Tree: This stage is the process of updating the Layer Tree
  • Paint: This stage is the process of preparing the drawing list for each layer
  • Composite Layers: This stage uses the drawing list to generate bitmaps of the corresponding Layers. It also involves Composite threads and rasterization. Raster in the Performence panel is the rasterization thread pool.

Here is just a brief introduction. If you don’t understand the content, you can refer to Li Bing’s article or my blog introducing the browser rendering process.

As you can see from this diagram, changing the left of the div triggers a Layout rearrangement. Let’s just change the background color of the div to give you a comparison.

Now let’s go back to the original question, how does the rearrangement occur when we get the offsetLeft attribute?


      
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
  <style>
    #test {
      width: 100px;
      height: 100px;
      background-color: red;
      position: relative;
    }
  </style>
</head>
<body>
  <div id="test">

  </div>
  <button onclick="reflow()">click</button>
  <script>
    function reflow() {
      var div = document.querySelector("#test");
      console.log(div.offsetLeft);
    }
  </script>
</body>
</html>
Copy the code

Let’s take a look at the rendering process using chrome tools

So let’s look at the following code:


      
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
  <style>
    #test {
      width: 100px;
      height: 100px;
      background-color: red;
      position: relative;
    }
  </style>
</head>
<body>
  <div id="test">

  </div>
  <button onclick="reflow()">click</button>
  <script>
    function reflow() {
      var div = document.querySelector("#test");
      div.style.left = '200px';
      console.log(div.offsetLeft);
    }
  </script>
</body>
</html>
Copy the code

We added a line of code and changed its left. Let’s see what happens


      
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
  <style>
    #test {
      width: 100px;
      height: 100px;
      background-color: red;
      position: relative;
    }
  </style>
</head>
<body>
  <div id="test">

  </div>
  <button onclick="reflow()">click</button>
  <script>
    function reflow() {
      var div = document.querySelector("#test");
      div.style.left = '200px';
      console.log(div.offsetLeft);
      div.style.left = '100px';
      console.log(div.offsetLeft);
      div.style.left = '200px';
      console.log(div.offsetLeft);
      div.style.left = '100px';
      console.log(div.offsetLeft);
    }
  </script>
</body>
</html>
Copy the code

How many rearrangements will result from this operation? Let’s take a look:


      
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
  <style>
    #test {
      width: 100px;
      height: 100px;
      background-color: red;
      position: relative;
    }
  </style>
</head>
<body>
  <div id="test">

  </div>
  <button onclick="reflow()">click</button>
  <script>
    function reflow() {
      var div = document.querySelector("#test");
      div.style.left = '200px';
      div.style.left = '100px';
      div.style.left = '200px';
      div.style.left = '100px';
      console.log(div.offsetLeft);
      console.log(div.offsetLeft);
      console.log(div.offsetLeft);
      console.log(div.offsetLeft);
    }
  </script>
</body>
</html>
Copy the code

Let’s look at the process

At the same time, in other blogs, I have noticed that these attributes can also cause rearrangements:

  • Activate CSS pseudo-classes (such as: :hover)
  • Setting the style property

However, after my attempts, simply changing non-geometric properties such as the background color does not cause the rearrangement. And if you’re going to change geometry, then I think it boils down to the first few cases. So I’m skeptical about this. If there are any students who would like to point out in the comments section.

Neither rearrangement nor redrawing

Speaking of rearranging and redrawing, don’t forget what we said at the beginning, that the most efficient way to do this is to skip rearranging and redrawing. You might be thinking, under what circumstances can you do that? In fact, this is the GPU acceleration we usually say, how to achieve it? During development, if we use certain attributes, the browser will help us promote the div that uses that attribute to a separate compositing layer, and in later rendering, the div that is promoted to that layer will skip the rearrangement and redrawing and go directly to the compositing phase. There was a question on Stack Overflow about this. The following attributes allow the browser to help us promote divs to a separate composition layer:

  • The layer has 3D or Perspective transform CSS properties
  • Video elements that use accelerated video decoding
  • Has a 3D (WebGL) context or a 2D context-accelerated Canvas element
  • Hybrid Plug-in (Flash)
  • Animate CSS animations on opacity of its own or use an animated Webkit transform element
  • Layers use accelerated CSS filters
  • Layers have descendants as synthetic layers
  • Layers have sibling elements with a lower Z-index that have compositing layers (in other words, that layer is rendered on top of the compositing layer)
  • The CSS will change attribute

I added the last point, and as we can see from the article, CSS3 hardware acceleration is a browser behavior, so it may have different manifestations in different browsers. Let’s use an example to understand. This is an example from li Bing’s column. I’ll borrow it for you. Note the will-change property in box:

<html>

  <head>
      <title>Observation will change</title>
      <style>
          .box {
              will-change: transform, opacity;
              display: block;
              float: left;
              width: 40px;
              height: 40px;
              margin: 15px;
              padding: 10px;
              border: 1px solid rgb(136, 136, 136);
              background: rgb(187, 177, 37).border-radius: 30px;
              transition: border-radius 1s ease-out;
          }
  
          body {
              font-family: Arial;
          }
      </style>
  </head>
  
  
  <body>
      <div id="controls">
          <button id="start">start</button>
          <button id="stop">stop</button>
      </div>
      <div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
          <div class="box">Rotate the box</div>
      </div>
      <script>
  
          let boxes = document.querySelectorAll('.box');
          let boxes1 = document.querySelectorAll('.box1');
          let start = document.getElementById('start');
          let stop = document.getElementById('stop');
          let stop_flag = false
  
  
          start.addEventListener('click'.function () {
              stop_flag = false
              requestAnimationFrame(render);
          })
  
  
          stop.addEventListener('click'.function () {
              stop_flag = true
          })
  
  
          let rotate_ = 0
          let opacity_ = 0
          function render() {
              if (stop_flag)
                  return 0
              rotate_ = rotate_ + 6
              if (opacity_ > 1)
                  opacity_ = 0
              opacity_ = opacity_ + 0.01
              let command = 'rotate(' + rotate_ + 'deg)';
              for (let index = 0; index < boxes.length; index++) { boxes[index].style.transform = command boxes[index].style.opacity = opacity_ } requestAnimationFrame(render);  }</script>
  </body>
  
  
  </html>
  
Copy the code

The Chrome tool gives us a way to see if we have promoted the layer

  • Flashing from paint will mark the rendered div with a green box
  • Layer Borders will yellow box the div rendered in the composite layer, that is, promoted to a separate layer
  • The FSP meter marks the frame rate of the current page

First we don’t add will-change to the animation, you can see the following effect

With this simple example, we can see the benefits of skipping rearrangements and redrawing. We can also use it in daily development

<style>
  .demo {
    transform: translateZ(0);
  }
</style>
Copy the code

To trick the browser into helping us promote div to a separate layer. However, it should be noted that although this method is good, it will also increase memory consumption. If too many unnecessary layers are separately promoted, the page will become sluggish. For details, please refer to this article.

For a more detailed introduction, please refer to the article wireless performance optimization: Composite shared by Amoy front-end team.

Write in the last

Ok, this is all the content of today, see the little lovely can help to like and pay attention to, let me have the motivation to continue to write ~ at the same time, any mistakes in the article also welcome to point out in the comment section, common discussion.