The blog has more excellent articles.

Rendering principle

Before we discuss performance tuning, it’s important to know a little bit about how browsers render. Different browsers have different approaches to rendering, but the general process is the same. Let’s take a look at the rendering process in Chrome.

Key render path

Key render paths are the series of steps that browsers must take to convert HTML, CSS, and JavaScript into a working site, which can be summarised as follows:

  1. Process HTML and build a DOM Tree.
  2. Process the CSS and build the CSSOM Tree.
  3. Merge DOM Tree and CSSOM Tree into Render Object Tree.
  4. Compute the geometric information of the nodes according to the Render Object Tree and layout them accordingly.
  5. Rendering the page requires building the Render Layer Tree to display the pages in the correct order, which is generated simultaneously with building the Render Object Tree. Then you build the Graphics Layer Tree to avoid unnecessary drawing and use hardware-accelerated rendering to eventually display the page on the screen.

DOM Tree

The DOM (Document Object Model) is an API Document used to render and interact with arbitrary HTML or XML. The DOM is a document model loaded into the browser that represents the document as a tree of nodes, each representing its constituent parts.

It is important to note that the DOM only builds the attributes and relationships of the document tags and does not specify the style elements need to be rendered, which is handled by CSSOM.

The build process

After the HTML byte data is obtained, the DOM Tree is constructed using the following process:

  1. Encoding: The HTML raw byte data is converted to a string with the specified encoding for the file.
  2. Lexical analysis (tokenization) : The process of scanning the input string verbatim, identifying words and symbols according to word-formation rules, and dividing them into comprehensible words (technically named Token).
  3. Parsers: The process of constructing a DOM Tree by applying HTML syntax rules to Tokens to pair tags, establish node relationships, and bind attributes.

Lexical analysis and parsing perform this procedure each time an HTML string is processed, such as using the document.write method.

Lexical analysis (tokenization)

The HTML structure is not too complicated, and in most cases the tags identified will have a start tag, a content tag, and an end tag, corresponding to an HTML element. In addition, there are DOCTYPE, Comment, EndOfFile and other markers.

Tokenization is achieved through a state machine model defined in the W3C.

To get a tag, you have to go through some state before you can finish parsing. Let’s walk through the process with a simple example.

<a href="www.w3c.org">W3C</a>
Copy the code

  • Opening mark:<a href="www.w3c.org">
    1. Data state: Enter Tag open state when < is encountered
    2. Tag open State: Indicates that a enters the Tag Name state
    3. Tag Name state: When a space is encountered, enter Before Attribute Name state
    4. Before Attribute name state: When h is encountered, the attribute name state is entered
    5. Attribute name state: Enter Before Attribute Value state when = is encountered
    6. “Before attribute value state:”, enter attribute value (double-letter) state
    7. Attribute value (double-letter) state: When w is encountered, keep the current state
    8. Letter of letter (letter from letter of letter) state: “After Attribute value (double-letter) state”
    9. Letter of ‘letter’ from letter of ‘letter’ (letter of ‘letter’ from letter of ‘letter’)
  • Content marking:W3C
    1. Data State: When encountering W, keep the current state and extract the content
    2. Data state: if < is encountered, enter Tag open state and complete parsing
  • Closing mark:</a>
    1. Tag open state: encounters/and enters End Tag open state
    2. End tag open state: if a is encountered, the tag name state is entered
    3. Tag name state: enter Data state

From the example above, you can see that the attribute is part of the start tag.

Parsing (parser)

After the parser is created, a Document object is associated as the root node.

I will briefly introduce the process, the specific implementation process can be viewed in Tree Construction.

As the parser runs, it iterates over Tokens. The Token is converted to the corresponding mode according to the type of the current Token, and then the Token is processed in the current mode. If the Token is a start tag, the corresponding element is created, added to the DOM Tree, and pushed into the start tag stack where the end tag has not yet been encountered. The main purpose of this stack is to implement the browser’s fault tolerance mechanism to correct nesting errors, as defined in the W3C. More markup processing can be seen in the state machine algorithm.

The resources

  1. How the browser works: Behind the scenes of the new Web browser — the combination of a parser and a lexical analyzer
  2. Browser rendering process and performance optimization – building DOM and CSSOM trees
  3. Behind the browser (a) – HTML language lexical parsing
  4. Behind the browser (2) – HTML language syntax parsing
  5. 50 lines of HTML compiler
  6. AST Parsing Basics: How to write a simple HTML parsing library
  7. HTML lexical analysis in WebKit
  8. HTML document parsing and DOM tree building
  9. How to build a DOM tree in a browser
  10. Building an Object Model — Document Object Model (DOM)

CSSOM Tree

loading

When a LINK tag is encountered during DOM Tree building, the browser immediately sends a request for the style file. Of course, we can also use inline or embedded styles directly to reduce requests; But will lose modularity and maintainability, and some optimization measures like cache and other ineffective, the advantages outweigh the disadvantages, cost performance is too low; This is not recommended unless it is to optimize operations such as home page loading.

blocking

Loading and parsing of CSS does not block the construction of the DOM Tree because the DOM Tree and the CSSOM Tree are two independent Tree structures. However, this process blocks the page rendering, meaning that the document will not be displayed on the page until the CSS is processed. The advantage of this strategy is that the page will not be rendered repeatedly; If the DOM Tree is built and rendered directly, the original style will be displayed, and when the CSSOM Tree is built, the rendering will suddenly change to a different style, which will be expensive and very poor user experience. In addition, the link flag blocks JavaScript, in which case the DOM Tree will not continue to build, because JavaScript will also block the DOM Tree, resulting in a long blank screen.

To illustrate this in more detail, use an example:


      
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <script>
    var startDate = new Date(a);</script>
  <link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css" rel="stylesheet">
  <script>
    console.log("link after script".document.querySelector("h2"));
    console.log("After" + (new Date() - startDate) + " ms");
  </script>
  <title>performance</title>
</head>
<body>
  <h1>The title</h1>
  <h2>Title 2</h2>
</body>
</html>
Copy the code

The first step is to set up Network throttling in the Network panel of the Chrome Console to slow down the Network for easier debugging.

The following figure shows that JavaScript really needs to be executed after the CSS has been loaded and parsed.

Why do we need to block JavaScript?

Because JavaScript can manipulate DOM and CSSOM, if the link tag doesn’t block JavaScript from running, then JavaScript will manipulate CSSOM, and there will be conflicts. More detailed instructions can be found in the article Adding interactions with JavaScript.

parsing

The steps of CSS parsing are very similar to HTML parsing.

Lexical analysis

CSS is broken down into the following tags:

Does CSS use hexadecimal rather than functional representation for color values?

The functional form is evaluated again and turned into a function token during lexical analysis, so using hexadecimal does seem to be an optimization.

Syntax analysis

Each CSS file or embedded style corresponds to a CSSStyleSheet authorStyleSheet object, which consists of a series of rules; Each Rule contains Selectors and a number of declearations, which consist of properties and values. In addition, the browser’s defaultStyleSheet and UserStyleSheet will also have corresponding CSSStyleSheet objects, because they are separate CSS files. As for inline styles, they are directly parsed into a Declearation collection when building a DOM Tree.

The difference between inline styles and authorStyleSheet

All of the authorStyleSheet is attached to the Document node, and we can get the collection in the browser through Document. styleSheets. Inline styles can be viewed directly from the node’s style property.

To understand the difference between inline styles and authorStyleSheet, take a look at an example:


      
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <style>
    body .div1 {
      line-height: 1em;
    }
  </style>
  <link rel="stylesheet" href="./style.css">
  <style>
    .div1 {
      background-color: #f0f;
      height: 20px;
    }
  </style>
  <title>Document</title>
</head>
<body>
  <div class="div1" style="background-color: #f00; font-size: 20px;">test</div>
</body>
</html>
Copy the code

As you can see, there are three CSSStyleSheet objects, and each CSSStyleSheet object has a CSSStyleDeclaration in its rules, and the direct CSSStyleDeclaration obtained by the inline style is the CSSStyleDeclaration.

Do you need to merge properties?

Attribute merge when parsing Declearation, a single declaration is converted into multiple declarations, such as:

.box {
  margin: 20px;
}
Copy the code

Margin: 20px will be converted into four statements; This shows that CSS advocates merging properties, but will eventually split them; So attribute merging should reduce the amount of CSS code.

To calculate

Why do we need to compute?

Because a node may have multiple selectors hitting it, it is necessary to combine all matched rules and set the final style.

The preparatory work

For ease of calculation, after the CSSStyleSheet object is generated, the Rules of the same type of the rightmost Selector of the CSSStyleSheet object will be stored in the corresponding Hash Map. For example, all Rules with a right-most Selector type of ID are stored in the ID Rule Map; The reason for using the rightmost Selector is to quickly match all the rules of the current element, and then each Rule checks to see if its next Selector matches the current element.

idRules
classRules
tagRules
...
*
Copy the code
Selector hit

If a node wants to obtain all matched rules, it needs to successively determine whether the Selector types (ID, class, tagName, etc.) in the Hash Map match the current node. If they match, all the rules of the current Selector type are filtered. Rules that match are found and placed in the result set; Note that wildcards are always filtered at the end.

Matches rules from right to left

As mentioned above, a Hash Map stores a Rule of the right-most Selector type, so when searching for a matching Rule, we check the right-most Selector of the current Rule. And if that passes, now we’re going to determine if the current Selector is the leftmost Selector; If yes, the match is successful, and the match is added to the result set. Otherwise, there’s still a Selector on the left, and we recursively check to see if the Selector on the left matches, and if it doesn’t, we move on to the next Rule.

Why do we need to match from right to left?

Let’s take div P. yellow and look for all of the div nodes. Then look down to see if the offspring are P nodes. If so, look down to see if there are any nodes that contain class=”yellow”. But doesn’t exist? A single query is wasted. If a page has thousands of DIV nodes and only one of them matches the Rule, you end up with a lot of invalid queries, and if most of the invalid queries are found at the end, the performance penalty is simply too great.

If a node wants to find a matching Rule, it will first check that the rightmost Selector is the Rule of the current node, and then check the Selector to the left. Under this matching rule, most invalid queries can be avoided in the first place, but of course performance is better and speed is faster.

Set the style

The order of setting styles is to inherit the parent node, then use the user-agent style, and then use the authorStyleSheet style.

AuthorStyleSheet priority

The Rule’s priority is calculated when the result set is put in. Take a look at the blink kernel’s definition of priority weights:

switch (m_match) {
  case Id: 
    return 0x010000;
  case PseudoClass:
    return 0x000100;
  case Class:
  case PseudoElement:
  case AttributeExact:
  case AttributeSet:
  case AttributeList:
  case AttributeHyphen:
  case AttributeContain:
  case AttributeBegin:
  case AttributeEnd:
    return 0x000100;
  case Tag:
    return 0x000001;
  case Unknown:
    return 0;
}
return 0;
Copy the code

Since rules are resolved from right to left, the order of precedence is calculated by adding the weights of the corresponding selectors in that order. Let’s look at a few examples:

/* * 65793 = 65536 + 1 + 256 */
#container p .text {
  font-size: 16px;
}

/* * 2 = 1 + 1 */
div p {
  font-size: 14px;
}
Copy the code

After all matched rules of the current node are put into the result set, they are sorted according to their priorities. If there are rules with the same priority, their positions are compared.

Inline style priority

The inline style will not be set until the Rule of authorStyleSheet is processed; Inline styles are handled when building the DOM Tree and placed on the node’s style property.

Inline styles are placed at the end of sorted result sets, so if not set! Important, inline styles have the highest priority.

! importantpriority

In setting up! Important will not include! All declarations of important are added to the end of the result set; Because this set is sorted in order of priority, so! The priority of important becomes the largest.

Write rules for the CSS

The resulting collection eventually generates ComputedStyle objects, all of which can be viewed using the window.getComputedStyle method.

It can be found that the statements in the figure are not in order, indicating that the maximum effect of writing rules is for good reading experience and teamwork.

Adjust the Style

This step will adjust the relevant declaration; For example, position: absolute; , the display of the current node is set to block.

The resources

  1. How to calculate CSS in Chrome
  2. Explore the CSS parsing principle
  3. Webkit kernel exploration [2] – Webkit CSS implementation
  4. Webkit CSS engine analysis
  5. Does CSS load block?
  6. CSS and JS block DOM parsing and rendering in this way
  7. External chain CSS lazy DOM parsing and DOMContentLoaded
  8. CSS/JS blocks DOM parsing and rendering
  9. Building an Object Model – CSS Object Model (CSSOM)
  10. Block rendered CSS

Render Object Tree

After the DOM Tree and CSSOM Tree are built, the Render Object Tree (the Document node is a special case) is generated.

Create a Render Object

When the Document node is created, a Render Object is created as the root. A Render Object is a visual Object that describes the position, size, and so on of a node.

Each of the display: None | contents of nodes will create a Render Object, process roughly as follows: Generate a ComputedStyle (described in the CSSOM Tree calculation section), and then compare the old and new computedStyles (the old ComputedStyle starts out empty by default); If not, create a new Render Object, associate it with the node currently being processed, and then establish a parent-child sibling relationship, thus forming a complete Render Object Tree.

Layout (rearrangement)

After the Render Object is added to the tree, it also needs to recalculate its position and size; ComputedStyle already contains this information, why do you need to recalculate? Margin: 0 auto; This declaration cannot be used directly. It needs to be converted to the actual size to draw nodes through the drawing engine. This is one of the reasons why DOM Tree and CSSOM Tree need to be combined into a Render Object Tree.

The layout is recursive from the Root Render Object, and each Render Object has a way to lay itself out. Why do you need to recursively calculate the position and size (that is, compute the child node and then go back to the parent node)? Because some layout information needs to be calculated by the child nodes first, the location and size of the parent node can be calculated from the layout information of the child nodes. For example, the height of the parent node needs to be supported by the children. What if the width of the child node is 50% of the height of the parent? This requires computing its own layout information before computing the child node, and then passing it to the child node. After the child node calculates the information, it will tell the parent node whether to recalculate.

Numeric types

All relative measurements (REM, EM, percentage…) Must be converted to absolute pixels on the screen. If it is EM or REM, the pixels need to be calculated based on the parent node or the root node. If it is a percentage, you need to multiply by the maximum width or height of the parent node. If it is auto, the values on both sides need to be calculated using (width or height of parent – width or height of current node) / 2.

The box model

As we all know, each element of the document is represented as a rectangular box (box model), which can clearly describe the layout structure of the Render Object; The box model has been vividly described in the source notes of Blink. Different from the familiar one, the scrollbar is also included in the box model, but not all browsers can change the size of the scrollbar.

// ***** THE BOX MODEL *****
// The CSS box model is based on a series of nested boxes:
// http://www.w3.org/TR/CSS21/box.html
//                              top
//       |----------------------------------------------------|
//       |                                                    |
//       |                   margin-top                       |
//       |                                                    |
//       |     |-----------------------------------------|    |
//       |     |                                         |    |
//       |     |             border-top                  |    |
//       |     |                                         |    |
//       |     |    |--------------------------|----|    |    |
//       |     |    |                          |    |    |    |
//       |     |    |       padding-top        |# # # # | | |
//       |     |    |                          |# # # # | | |
//       |     |    |    |----------------|    |# # # # | | |// | | | | | | | | | // left | ML | BL | PL | content box | PR | SW | BR | MR | // | | | | | | | | | // | | | |----------------| | | | | // | | | | | | | // | | | padding-bottom | | | | // | | |--------------------------|----| | |  // | | |# # # # | | | |
//       |     |    |     scrollbar height ####| SC | | |
//       |     |    |                      # # # # | | | |
//       |     |    |-------------------------------|    |    |
//       |     |                                         |    |
//       |     |           border-bottom                 |    |
//       |     |                                         |    |
//       |     |-----------------------------------------|    |
//       |                                                    |
//       |                 margin-bottom                      |
//       |                                                    |
//       |----------------------------------------------------|
//
// BL = border-left
// BR = border-right
// ML = margin-left
// MR = margin-right
// PL = padding-left
// PR = padding-right
// SC = scroll corner (contains UI for resizing (see the 'resize' property)
// SW = scrollbar width
Copy the code
box-sizing

Box – sizing: content – box | border – box, the content – box box model, follow the standard W3C border – box comply with IE and box model.

The difference is that the Content-box contains only the Content Area, while the border-box contains all the way to the border. Through an example:

// width
// content-box: 40
// border-box: 40 + (2 * 2) + (1 * 2)
div {
  width: 40px;
  height: 40px;
  padding: 2px;
  border: 1px solid #ccc;
}
Copy the code

The resources

  1. How to view layout from Chrome source
  2. Chromium Web Render Object Tree creation process analysis
  3. How browsers work: Behind the scenes of the new Web browser — rendering trees and DOM trees
  4. Talk about my understanding of box models
  5. Render tree construction, layout, and drawing

Render Layer Tree

The Render Layer is generated when the Render Object is created, and the Render Object with the same coordinate space belongs to the same Render Layer. This tree is mainly used to implement the cascading context to ensure that the pages are composited in the correct order.

Create a Render Layer

A Render Object that meets the cascading context condition will definitely create a new Render Layer for it, but some special Render objects will also create a new Render Layer.

The Render Layer is created for the following reasons:

  • NormalLayer
    • Position attributes are relative, fixed, sticky, and Absolute
    • Opacity < 1, filter, mask, mix-blending-mode (not normal)
    • Clip-path
    • 2D or 3D conversion (transform not none)
    • Hide the back (backface-visibility: hidden)
    • Box-reflect
    • Column-count (not auto) or column-widthz (not auto)
    • Animations are applied to opacity, transform and filter
  • OverflowClipLayer
    • Cut and overflow content (Overflow: Hidden)

In addition, the Render Object corresponding to the following DOM elements also creates a separate Render Layer:

  • Document
  • HTML
  • Canvas
  • Video

If it is a NoLayer, it does not create the Render Layer, but shares it with the first parent node that owns the Render Layer.

The resources

  1. Wireless performance optimization: Composite — from LayoutObjects to PaintLayers
  2. Chromium Webpage Render Layer Tree creation process analysis
  3. WEBKIT renders the four indispensable trees

Graphics Layer Tree

Software rendering

Software rendering is the earliest rendering method adopted by browsers. In this way, the Render Layer is drawn from back to front (recursively); During the rendering of a Render Layer, its Render Objects continually send drawing requests to a shared Graphics Context to draw themselves into a shared bitmap.

Hardware rendering

Special Render layers that draw to their own back-end storage (the current Render Layer has its own bitmap) rather than the bitmap shared by the entire web page are called the Composited Layer (Graphics Layer). Finally, after all the Composited layers are drawn, they are Composited into a final bitmap, a process called Compositing. This means that if a Render Layer becomes a Composited Layer, the entire page can only be rendered by compositing. In addition, Compositing also includes transform, scale, opacity and other operations, so this is why hardware acceleration is good. Animation operations above do not need to be redrawn, only recomposed.

As mentioned above, software rendering will only have a Graphics Context, and all Render layers will use the same Graphics Context to draw. However, hardware rendering requires the combination of multiple bitmaps to obtain a complete image, which requires the introduction of Graphics Layer Tree.

The Graphics Layer Tree is created based on the Render Layer Tree, but not every Render Layer has a Composited Layer; This is because creating a large number of Composited layers consumes a lot of system memory, so to become a Composited Layer the Render Layer must give a reason for creating it, which in effect describes the features of the Render Layer. If a Render Layer is not a Compositing Layer, it shares one with its ancestor.

Each Graphics Layer will have a corresponding Graphics Context. The Graphics Context outputs the bitmap of the current Render Layer. The bitmap is stored in the system memory and uploaded to the GPU as a texture (which can be understood as the bitmap in the GPU). Finally, the GPU synthesizes multiple bitmaps and draws them to the screen. Since the Graphics Layer has separate bitmaps, hardware rendering does not normally redraw the relevant Render Layer when updating a web page as software rendering does; Instead, redraw the Graphics Layer where the update occurred.

Ascension causes

The reasons for upgrading the Render Layer to the Composited Layer are outlined below, and more detailed wireless performance optimizations can be seen: Composite — from PaintLayers to GraphicsLayers.

  • The iframe element has a Composited Layer.
  • The video element and its control bar.
  • Use WebGL’s Canvas element.
  • Hardware accelerated plug-ins, such as Flash.
  • 3D or Perspective Transform CSS properties.
  • Backface – visibility is hidden.
  • Animation or transition is applied to opacity, Transform, fliter, and backdropfilter (animation or transition must be active, When the animation or Transition effect does not start or end, the promoted Composited Layer reverts to normal.
  • Will-change is set to opacity, transform, top, left, bottom, and right (top, left, etc., should be set with clear positioning attributes, such as relative, etc.).
  • Has descendants of the Composited Layer and has certain attributes of its own.
  • Element has a sibling element of the Composited Layer with a lower Z-index.
Why Composited Layer is required?
  1. Avoid unnecessary redrawing. For example, there are two layers A and B in the web page. If the elements of a Layer change, b Layer does not change. Just redraw a Layer and make Compositing with B Layer to get the entire page.
  2. Implement some UI features efficiently with hardware acceleration. For example, scrolling, 3D transformation, transparency or filter effects can be efficiently implemented using the GPU (hardware rendering).
Layer compression

Due to overlapping, a large number of Composited layers may be generated, which wastes a lot of resources and severely affects performance, a problem known as Layer explosion. The browser addresses this with Layer Squashing, when multiple Render layers overlap with the Composited Layer, the Render layers are compressed into the same Composited Layer. Here’s an example:


      
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <style>
    div {
      position: absolute;
      width: 100px;
      height: 100px;
    }
    .div1 {
      z-index: 1;
      top: 10px;
      left: 10px;
      will-change: transform;
      background-color: #f00;
    }
    .div2 {
      z-index: 2;
      top: 80px;
      left: 80px;
      background-color: #f0f;
    }
    .div3 {
      z-index: 2;
      top: 100px;
      left: 100px;
      background-color: #ff0;
    }
  </style>
  <title>Document</title>
</head>
<body>
  <div class="div1"></div>
  <div class="div2"></div>
  <div class="div3"></div>
</body>
</html>
Copy the code

You can see that the next two nodes overlap and are compressed into the same Composited Layer.

Some cases that cannot be compressed can be seen in wireless Performance Optimization: Composite – Layer Compression.

The resources

  1. Wireless Performance optimization: Composite — from -PaintLayers — to -Graphicslayers
  2. Webkit rendering basics with hardware acceleration
  3. Chromium Web Graphics Layer Tree creation process analysis
  4. Hardware-accelerated composition in Chrome
  5. Detailed analysis of browser rendering process
  6. WebKit rendering process basics and layering acceleration

Performance optimization

After a brief introduction to the various components of the browser rendering process, let’s explore how to optimize updates caused by visual changes through the pixel pipeline.

Pixel pipe

JavaScript. In general, we will use JavaScript to achieve some 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 is 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.

To draw. 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).

Synthesis. 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.

Every frame rendered is processed through each part of the pipeline, but it does not mean that all parts are executed. In fact, there are usually three ways for pipes to implement visual changes for a given frame:

  1. JS/CSS > Styles > Layout > Draw > Composition

If you change the Layout property of a DOM element, that is, changing the style of the element (width, height, position, etc.), the browser checks which elements need to be rearranged and fires a reflow on the page to complete the rearrangement. The reflow elements will then trigger the drawing process, which will eventually trigger the layer merge process to produce the final image.

  1. JS/CSS > Styles > Draw > Composition

If you change the Paint Only property of a DOM element, such as the background image, text color, or shadow, these properties do not affect the layout of the page, so the browser skips the layout process after calculating the style, and Only draws and renders the layer merge process.

  1. JS/CSS > Styles > Composition

If you modify a CSS property that is not styled and not drawn, the browser will skip the layout and drawing process after the style calculation and go straight to the render layer merge. This approach is optimal in terms of performance, and we strive for a third rendering process for heavy rendering such as animation and scrolling.

Properties affecting Layout, Paint, and Composite are available via the CSS Triggers website.

The refresh rate

As mentioned above, each frame is pixelated, which means that each frame is re-rendered. We need to introduce another concept: refresh rate.

Refresh rate is an indicator of how many times a second you can re-render. Most current devices have a screen refresh rate of 60 times a second; So if you have animations, gradients, and scrolling on the page, the interval between each re-rendering of the browser must be consistent with each refresh of the device in order to be smooth. It is important to note that most browsers also place a limit on the time interval between rerendering, as exceeding the screen refresh rate will not improve the user experience.

The refresh rate (Hz) depends on the hardware level of the monitor. The frame rate (FPS) depends on the graphics card or software.

Each re-rendering time should not exceed 16.66ms (1 second / 60 times). But in reality, the browser has a lot of tidied up to do, so it’s best to do all of our work in less than 10 milliseconds. If you exceed that time, the refresh rate drops, which can cause the page to shake and feel sluggish.

Optimizing JavaScript execution

JavaScript is the main trigger for visual changes, and bad timing or long running JavaScript can be a common cause of performance degradation. Here are some common optimizations for JavaScript execution.

window.requestAnimationFrame

In the absence of a requestAnimationFrame method, we might use setTimeout or setInterval to trigger visual changes to perform the animation; The problem with this approach is that the time of the callback is not fixed, it may be right at the end, or it may not be executed at all, often resulting in frame loss and page stalling.

The root cause of this problem is timing, when the browser needs to know when to respond to the callback function. SetTimeout or setInterval uses a timer to trigger a callback function. However, a timer cannot be guaranteed to execute correctly. There are many factors that affect its running time, such as: When a synchronous code is executed, the system waits until the synchronous code is finished and there are no other tasks in the asynchronous queue. Also, we know that the optimal time for each re-render is about 16.6ms. If the timer interval is too short, it will cause overrendering and increase overhead. Too long will delay rendering and make the animation not smooth.

RequestAnimationFrame differs from setTimeout or setInterval in that it is up to the system to determine when the callback should be executed, asking the browser to execute the callback before the next rendering. Regardless of the refresh rate of the device, the requestAnimationFrame interval follows the time it takes to refresh the screen once; For example, if the refresh rate of a device is 75 Hz, then the time interval is 13.3 ms (1 second / 75 times). Note that this method ensures that the callback function is rendered only once per frame, but if there are too many tasks in that frame, it will still cause a lag. Therefore, it can only ensure that the minimum interval between rerenders is the screen refresh time.

The requestAnimationFrame method is described in the MDN documentation, and an example of a web animation is used to see how it can be used.

let offsetTop = 0;
const div = document.querySelector(".div");
const run = (a)= > {
  div.style.transform = `translate3d(0, ${offsetTop += 10}px, 0)`;
  window.requestAnimationFrame(run);
};
run();
Copy the code

If you want to animate, you must call the requestAnimationFrame method again each time the callback function is executed. In the same way as setTimeout does, but without the time interval.

The resources
  1. The requestAnimationFrame is hailed as a magic artifact
  2. How much does requestAnimationFrame know?
  3. RequestAnimationFrame up.the
  4. Say goodbye to the timer, toward the window. RequestAnimationFrame ()
  5. RequestAnimationFrame performs better
  6. Talk about the animation loop for requestAnimationFrame

window.requestIdleCallback

The requestIdleCallback method only executes the callback function when there is an idle time at the end of a frame; It’s great for tasks that need to be handled while the browser is idle, such as statistical uploads, data preloading, template rendering, and so on.

In the past, if you needed to process complex logic without sharding, the user interface would probably be in a state of suspended animation, and any interaction would be invalid. In this case, setTimeout can be used to split the task into multiple modules, processing only one module at a time, which can greatly alleviate this problem. However, this approach is highly uncertain, we do not know whether the frame is free or not, if it is already jammed with a bunch of tasks, then it is not appropriate to process the module. Therefore, in this case, we can also use the requestIdleCallback method to make the most efficient use of idle to process sharding tasks.

Does requestIdleCallback have to wait forever if there is no idle time? Of course not, it takes an optional configuration object in addition to the callback function, which can be set to a timeout using the timeout property. When this time is reached, the requestIdleCallback callback is immediately pushed into the event queue. Here’s how to use it:

// Task queue
const tasks = [
  (a)= > {
    console.log("First mission."); = > {}, ()console.log("Mission Two."); = > {}, ()console.log("Mission Three."); },];// Set the timeout period
const rIC = (a)= > window.requestIdleCallback(runTask, {timeout: 3000})

function work() {
  tasks.shift()();
}

function runTask(deadline) {
  if (
    (
      deadline.timeRemaining() > 0 ||
      deadline.didTimeout
    ) &&
    tasks.length > 0
  ) {
    work();
  }

  if (tasks.length > 0) {
    rIC();
  }
}

rIC();
Copy the code

A detailed description of the callback function parameters can be found in the MDN documentation.

Change the DOM

DOM should not be changed in the callback function of the requestIdleCallback method. Let’s look at the position at the end of a frame where the callback is triggered:

The callback function is scheduled after the frame is submitted, which means that the rendering is done and the layout has been recalculated; If we change the style in the callback and read the layout information in the next frame, then all the layout calculations we did before are wasted and the browser forces the layout calculation to be redone, also known as forced synchronous layout.

If you really want to modify the DOM, the best practice is to build the Document Fragment in the callback of the requestIdleCallback and then make the actual DOM changes in the requestAnimationFrame callback of the next frame.

Fiber

React 16 introduced a new Reconciler, Fiber Reconciler. It differs from the original Stack Reconciler in that the entire rendering process is not completed continuously and without interruption; Instead, the task is fragmented and segmented, which is implemented using the requestIdleCallback and requestAnimationFrame methods. RequestIdleCallback is responsible for lower-priority tasks, and requestAnimationFrame is responsible for higher-priority animation-related tasks.

The resources
  1. RequestIdleCallback – Background task scheduling
  2. You should know about requestIdleCallback
  3. Using requestIdleCallback
  4. React Fiber — Reconciliation

Web Worker

JavaScript uses a single-threaded model, which means that all tasks are done on a single thread and only one task can be executed at a time. Sometimes, we need to deal with a lot of computational logic, which can be time consuming, and the user interface can be in a state of suspended animation, which can be very bad for the user experience. At this point, we can use the Web Worker to handle these calculations.

Web Workers are specifications defined in HTML5 that allow JavaScript scripts to run in background threads outside of the main thread. This creates a multithreaded environment for JavaScript. On the main thread, we can create the Worker thread and assign some tasks to it. The Worker thread and the main thread run at the same time without interference. When the Worker thread completes the task, it sends the result to the main thread.

Web workers are more of a callback mechanism than a multithreaded environment. After all, Worker threads can only be used for calculations and cannot perform operations such as DOM changes; It also does not share memory and has no concept of thread synchronization.

The advantages of Web workers are obvious. It can free up the main thread to better respond to user interactions without being blocked by some computationally intensive or high-latency tasks. However, Worker threads are also resource-intensive because once created, they continue to run without being interrupted by user actions. So when the task completes, the Worker thread should be closed.

Web Workers API

A Worker thread is created by calling the Worker() constructor with the new command; The constructor takes a script file containing the code to perform the task, and the URI importing the script file must comply with the same origin policy.

The Worker thread is not in the same global context as the main thread, so there are a few caveats:

  • The two can not communicate directly, must pass the data through the message mechanism; Also, the data is copied during this process rather than shared through instances created by the Worker. You can view the receiving and sending of data in worker: Details.
  • You cannot use DOM,windowparentThese objects, but you can use things that are independent of the main thread global context, for exampleWebScoket,indexedDBnavigatorThese objects, more usable objects can be viewedFunctions and classes available to Web Workers.
use

Two different types of threads are defined in the Web Worker specification; One is a Dedicated Worker (Dedicated thread), it is the global context of DedicatedWorkerGlobalScope object; The other is the SharedWorker, whose global context is the SharedWorkerGlobalScope object. The Dedicated Worker can only be used on one page, while the Shared Worker can be Shared by multiple pages.

Let me briefly introduce how to use it. More APIS can be viewed using Web Workers.

Special thread

The most important part of the following code is how messages are sent and received between two threads, both of which use the postMessage method to send messages and the onMessage event to listen for them. The difference is that in the main thread, the onMessage event and postMessage methods must be mounted on the Worker instance; In Worker threads, the instance method of Worker itself is mounted in the global context.


      
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Web worker-specific threads</title>
</head>
<body>
  <input type="text" name="" id="number1">
  <span>+</span>
  <input type="text" name="" id="number2">
  <button id="button">determine</button>
  <p id="result"></p>

  <script src="./main.js"></script>
</body>
</html>
Copy the code
// main.js

const number1 = document.querySelector("#number1");
const number2 = document.querySelector("#number2");
const button = document.querySelector("#button");
const result = document.querySelector("#result");

// 1. Specify a script file to create an instance of Worker
const worker = new Worker("./worker.js");

button.addEventListener("click", () = > {// 2. Click the button to send two numbers to the Worker thread
  worker.postMessage([number1.value, number2.value]);
});

// 5. Listen for messages returned by Worker threads
// We know that there are two ways to bind events, using the addEventListener method and mounting them directly to the corresponding instance
worker.addEventListener("message", e => {
  result.textContent = e.data;
  console.log("Executed");
})
Copy the code
// worker.js

// 3. Listen for messages sent by the main thread
onmessage = e= > {
  console.log("Start background task");
  const result= +e.data[0]+ +e.data[1];
  console.log("End of calculation.");

  // 4. Return the result to the main thread
  postMessage(result);
}
Copy the code
Shared thread

Shared threads can be shared across multiple pages, but they must comply with the same origin policy, which means they can only be used on pages with the same protocol, host, and port number.

The example is basically similar to dedicated threads, except that:

  • The constructor for creating the instance is different.
  • The main thread must communicate with the shared thread through an exactly open port object; Both need to pass before a message can be deliveredonmessageEvent or explicit callstartMethod to open a port connection. This is done automatically in dedicated threads.
// main.js

const number1 = document.querySelector("#number1");
const number2 = document.querySelector("#number2");
const button = document.querySelector("#button");
const result = document.querySelector("#result");

// 1. Create a shared instance
const worker = new SharedWorker("./worker.js");

// 2. Explicitly open the port connection with the start method of the port object, since the onMessage event is not used below
worker.port.start();

button.addEventListener("click", () = > {// 3. Send messages through the port object
  worker.port.postMessage([number1.value, number2.value]);
});

// 8. Listen for the result returned by the shared thread
worker.port.addEventListener("message", e => {
  result.textContent = e.data;
  console.log("Executed");
});
Copy the code
// worker.js

// 4. Listen for port connections through the onConnect event
onconnect = function (e) {
  // 5. Obtain the port using the ports attribute of the event object
  const port = e.ports[0];

  // 6. Listen for messages sent from the main thread through the onMessage event of the port object and implicitly open the port connection
  port.onmessage = function (e) {
    console.log("Start background task");
    const result= e.data[0] * e.data[1];
    console.log("End of calculation.");
    console.log(this);

    // 7. Return the result to the main thread through the port objectport.postMessage(result); }}Copy the code
The resources
  1. Optimize JavaScript execution — reduce complexity or use Web workers
  2. Use Web Workers
  3. In-depth HTML5 Web Worker practices: Multithreaded programming
  4. JS and Multithreading

Anti-shake and throttling functions

Event callbacks are frequently triggered when changing the size of a window, scrolling a web page, or entering content, adding a significant burden to the browser and resulting in a poor user experience. At this point, we can consider using anti-shake and throttling functions to handle such callbacks without affecting the actual interaction.

Let’s take a quick look at these two functions:

  • The debounce function. Event callbacks are not performed while events are continuously emitted; The event callback is executed only once if no more events are raised for a period of time.

  • Throttle functions. Event callbacks are also executed at repeated intervals when an event is continuously fired.

The biggest difference between the two functions is the timing of execution. The anti-shake function will execute the event callback after the event is triggered and stopped for a period of time. Throttling functions perform event callbacks after a continuous interval of time when the event is triggered. We use timers to implement both functions briefly. For detailed versions, see Underscore and Lodash — debounce and Lodash — Throttle. The throttling function is actually better used to call the event callback after the browser has the requestAnimationFrame method.

Realize the anti – shake function

Each time the debounce return is executed, the previous timer is cleaned up and a new timer is run again. When the returned function is executed for the last time, the timer will not be cleaned up, and you can normally wait for the timer to end and execute the event callback.

function debounce(func, wait) {
  let timeout = null;
  
  return function run(. args) {
    clearTimeout(timeout);
    timeout = setTimeout((a)= > {
      func.apply(this, args); }, wait); }};Copy the code
Implement throttling function

Do not regenerate the timer while it exists. Wait until the timer ends, the event callback is executed, the timer is empty; The next time you execute the function returned by throttle, it regenerates into a timer and waits for the next event callback to execute.

function throttle(func, wait) {
  let timeout = null;

  return function run(. args) {
    if(! timeout) { timeout = setTimeout((a)= > {
        timeout = null;
        func.apply(this, args); }, wait); }}}Copy the code
The resources
  1. JS anti – shake and throttling
  2. Causes the input handler to remove jitter
  3. Underscore
  4. Lodash – debounce
  5. Lodash – throttle

Reduce the complexity of Style

We know that the most important components of CSS are selectors and declarations, so I’ll talk about how to reduce the complexity of styles in these two areas.

Avoid selector nesting

As we learned in the CSSOM Tree section, nested selectors match from right to left, which is a recursive process, and recursion is a time-consuming operation. Not to mention some CSS3 selectors that require more computation, such as:

.text:nth-child(2n) .strong {
  /* styles */
}
Copy the code

To determine which nodes apply this style, the browser must first ask is this a node with a “strong” class? A “text” class node whose parents happen to be even? So much computation can be avoided with a simple class:

.text-even-strong {
  /* styles */
}
Copy the code

With such a simple selector, the browser only needs to match it once. To accurately describe considerations such as web page structure, reusability, and code sharing, we can use BEM to assist in development.

BEM (Block, element, modifier)

BEM is simply a naming convention for a class that suggests a single class for all elements and that nesting can be well organized within a class:

.nav {}
.nav__item {}
Copy the code

If a node needs to be distinguished from other nodes, modifiers can be added to assist in development:

.nav__item--active {}
Copy the code

See Get BEM for a more detailed description and usage.

Use a less expensive style

Because the screen looks different, the browser’s rendering overhead for each style will be different. For example, it would certainly take longer to draw a shadow than a normal background. So let’s compare the cost between the two.


      
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <style>
    .simple {
      background-color: #f00;
    }
    .complex {
      box-shadow: 0 4px 4px rgba(0, 0, 0, 0.5);
    }
  </style>
  <title>Performance optimization</title>
</head>
<body>
  <div class="container"></div>
  <script>
    const div = document.querySelector(".container");
    let str = "";
    for (let i = 0; i < 1000; i++) {
      str += "
       
background-color: #f00;
"
; / / STR + = "< div class = \" complex \ "> box - shadow: 0, 4 px, 4 px, rgba (0,0,0,0.5); "; } div.innerHTML = str;
</script> </body> </html> Copy the code

As you can see, the Layout of shadow is 31.35ms and the paint is 6.43ms. The background Layout is 10.81ms and the paint is 4.30ms. The Layout difference is quite obvious.

Therefore, if possible, you should use a less expensive style instead of the current style to achieve the end result.

The resources

  1. Reduce the scope and complexity of style calculations
  2. CSS BEM writing specification

Reflow and Repaint

First let’s look at what rearrangement and redraw are.

  • Rearrangement is the process of reconstructing part or all of the Render Object Tree to compute the layout due to changing the style or adjusting the DOM structure. This process is triggered at least once, when the page is initialized.
  • Redraw refers to redrawing the affected part of the screen.

Observing pixel channels, it can be found that redrawing does not necessarily trigger rearrangement. For example, changing the background color of a node will only redraw the node, but not rearrangement, because the layout information has not changed. But rearrangement will definitely trigger redrawing.

The following situations can cause rearrangement or redrawing:

  • Adjust DOM structure
  • Modifying CSS Styles
  • User events, such as page scrolling, changing window size, etc

Browser Optimization Strategy

It is inevitable that rearrangements and redraws will be triggered constantly. However, they are very resource-intensive and are the root cause of poor web page performance.

Improving web performance means reducing the frequency and cost of rearranging and redrawing, and triggering rerendering as little as possible.

Browsers have an optimization strategy for centralized DOM operations: create a queue of changes, execute it once, and render it once.

div2.style.height = "100px";
div2.style.width = "100px";
Copy the code

The code above will only render once after the browser is optimized. However, if the code is poorly written, the changing queue is flushed immediately and rendered; This is usually when style information is retrieved immediately after DOM modification. The following style information triggers rerendering:

  • offsetTop/offsetLeft/offsetWidth/offsetHeight
  • scrollTop/scrollLeft/scrollWidth/scrollHeight
  • clientTop/clientLeft/clientWidth/clientHeight
  • getComputedStyle()

Tips for improving performance

  1. Take advantage of browser optimization strategies. The same DOM operations (read or write) should be put together. Do not insert write operations between read operations.
  2. Don’t calculate styles too often. If a style is rearranged, it is best to cache the results. Avoid rearranging it the next time you use it.
// Bad
const div1 = document.querySelector(".div1");
div1.style.height = div1.clientHeight + 200 + "px";
div1.style.width = div1.clientHeight * 2 + "px";

// Good
const div2 = document.querySelector(".div2");
const div2Height = div1.clientHeight + 200;
div2.style.height = div2Height + "px";
div2.style.width = div2Height * 2 + "px";
Copy the code
  1. Don’t change styles line by line. By changing theclassNamecssTextProperty to change style once and for all.
// Bad
const top = 10;
const left = 10;
const div = document.querySelector(".div");
div.style.top = top + "px";
div.style.left = left + "px";

// Good
div.className += "addClass";

// Good
div.style.cssText += "top: 10px; left: 10px";
Copy the code
  1. Use the offline DOM. Offline means that no operation is performed on the real node. You can do this in the following ways:
  • Manipulate the Document Fragment object and add it to the DOM Tree when you’re done
  • usecloneNodeMethod, perform operations on the cloned node, and then replace the original node with the cloned node
  • Set the node todisplay: none;(requires a rearrangement), then perform multiple operations on the node, and finally restore the display (requires a rearrangement). In this way, two rearrangements are used and more re-renders are avoided.
  • Set the node tovisibility: hidden;And set it todisplay: none;Is similar, but this property is only optimized for redrawing, not reordering, because it is hidden while the node is still in the document flow.
  1. Set up theposition: absolute | fixed;. The nodes will leave the document flow, and the rearrangement will be less expensive because you don’t have to worry about the impact of this node on other nodes.
  2. Use virtual DOM, such as Vue, React, etc.
  3. Use flexbox layout. Flexbox layouts have much higher performance than traditional layout models, and here’s a look at 1000 of themdivNode applicationfloatflexCost comparison of layout. You can see that for the same number of elements and the same visual appearance,flexThe layout of the overhead is much smaller (37.92 ms | float flex 13.16 ms).

The resources

  1. Page performance management details
  2. Render optimization: Rearrange redraw with hardware acceleration
  3. Detailed analysis of browser rendering process
  4. CSS Animation performance optimization

The optimization of Composite

Finally, we reach the end of the pixel pipeline. For this part of the optimization strategy, we can start from why we need the Composited Layer (Graphics Layer). This problem has been explained in the Graphics Layer Tree construction, now a brief review:

  1. Avoid unnecessary redrawing.
  2. Implement some UI features efficiently with hardware acceleration.

According to the two characteristics of the Composited Layer, the following optimization measures can be summarized.

usetransformopacityProperty to animate

As mentioned above, the Layout and Paint parts of the pixel pipeline can be skipped and only Composite can be made. The way to do this is simply to use CSS properties that only trigger a Composite; Currently, the only CSS properties that satisfy this condition are transform and opacity.

Using transform and opacity, the element must be a Composited Layer; If not, Paint will fire as usual (depending on Layout, transform will normally fire). Here’s an example:


      
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <style>
    .div {
      width: 100px;
      height: 100px;
      background-color: #f00;
      /* will-change: transform; * /
    }
  </style>
  <title>Performance optimization</title>
</head>

<body>
  <div class="div"></div>
  <script>
    const div = document.querySelector(".div");
    const run = (a)= > {
      div.style.transform = "translate(0, 100px)";
    };
    setTimeout(run, 2000);
  </script>
</body>
</html>
Copy the code

We will use transform to shift down. We will not initially promote the div node to a Composited Layer; As you can see from the image below, Layout and Paint are still triggered.

At this point, by promoting the div node to the Composited Layer, we see that Layout and Paint have been skipped, as we expected.

Reduce the area to draw

If we can’t avoid drawing, we should minimize the amount of repainting we need. For example, if there is a fixed area at the top of the page, when some other area of the page needs to be redrawn, it is likely that the entire screen will need to be redrawn, and the fixed area will also be affected. In this case, the areas that need to be redrawn or affected can be promoted to a Composited Layer to avoid unnecessary drawing.

The best way to advance to a Composited Layer is to use the WILL-change property of CSS, which can be described in the MDN documentation.

.element {
  will-change: transform;
}
Copy the code

For unsupported browsers, the easiest way to hack is to use 3D morphing to upgrade to a Composited Layer.

.element {
  transform: translateZ(0);
}
Copy the code

Based on the previous example, we tried to use the will-change attribute to avoid redrawing a fixed area.


      
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <style>
    .div {
      width: 100px;
      height: 100px;
      background-color: #f00;
    }
    .header {
      position: fixed;
      z-index: 9999;
      width: 100%;
      height: 50px;
      background-color: #ff0;
      /* will-change: transform; * /
    }
  </style>
  <title>Performance optimization</title>
</head>

<body>
  <header class="header">Fixed area</header>
  <div class="div">Changes in regional</div>
  <script>
    const div = document.querySelector(".div");
    const run = (a)= > {
      div.style.opacity = 0.5;
    };
    setTimeout(run, 2000);
  </script>
</body>
</html>
Copy the code

First, let’s look at the unoptimized case; Along with instructions to view the details of a browser frame drawing.

  1. The Performance screen of the console is displayed.
  2. Click Settings (Mark 1) to turn on the draw analyzer (Mark 2).
  3. Start the Record (tag 3) and when you get the information you want, click Stop (tag 4) to Stop the Record.
  4. Click Paint on this frame (tag 5) to see the drawing details.
  5. Switch to the Paint Profiler TAB (tag 6) to see the steps for drawing.

As you can see from the images above (markers 7 and 8), the fixed area is indeed affected and redrawn. We then compared the case optimized with will-change and found that the fixed area did not trigger redraw.

Also, we can view the layout details of a frame (tag 1) (Tag 2) to see if the fixed area (tag 3) is promoted to a Composited Layer (tag 4) to avoid unnecessary drawing.

Manage the Composited Layer properly

Upgrading to a Composited Layer does optimize performance; However, remember that creating a new Composited Layer requires extra memory and management, which is very expensive. So, on devices with limited memory resources, the performance gain of a Composited Layer is likely to be far less than the cost of creating multiple Composited Layers. Meanwhile, the bitmap of each Composited Layer needs to be uploaded to GPU. Therefore, it is unavoidable to consider the bandwidth between CPU and GPU and how much memory is used to process GPU textures.

We compared the memory usage of the normal Layer with that of the Composited Layer with 1000 div nodes. You can see that the gap is still quite obvious.

Minimal lifting

From the above, we know that more Composited layers is not always better. In particular, don’t upgrade every element of the page with the following code, the resource consumption would be terrible.

* {
  /* or transform: translateZ(0) */
  will-change: transform;
}
Copy the code

Minimizing promotion means minimizing the number of Composited layers on a page. To do this, properties such as will-change that elevate nodes to a Composited Layer can be written out of the default state. The reasons for this will be explained below.

In this example, we write the will-change property in the default state; Then, compare the render with the attribute removed.

.box {
  width: 100ox;
  height: 100px;
  background-color: #f00;
  will-change: transform;
  transition: transform 0.3 s;
}
.box:hover {
  transform: scale(1.5);
}
Copy the code

Composited Layer promoted with will-change:

Normal layers:

We found that the only difference was that when the animation started and ended, redraw was triggered; When the animation is running, there is no difference between deleting or using will-change.

We talked about this reason when building the Graphics Layer Tree:

Animation or transition is applied to opacity, Transform, fliter, and backdropfilter (animation or transition must be active, When the animation or Transition effect does not start or end, the promoted Composited Layer reverts to normal.

This reason gives us the right to dynamically promote the Composited Layer; Therefore, we should take advantage of this to reduce the number of unwanted Composited layers.

Protective layer

We covered Layer explosions in the Graphics Layer Tree, which refers to the problem of having a large number of additional Composited layers due to overlap. Browser Layer compression solves this problem to a large extent, but there are special cases where the Composited Layer cannot be compressed; This is likely to result in a few Composited layers that are not what we expected, which means a lot of additional Composited layers.

In the layer compression section, we gave examples of using layer compression optimization, which will not be repeated here. Let’s take a closer look at preventing layer explosions by solving an example that cannot be compressed by layers.


      
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <style>
    .animating {
      width: 300px;
      height: 30px;
      line-height: 30px;
      background-color: #ff0;
      will-change: transform;
      transition: transform 3s;
    }

    .animating:hover {
      transform: translateX(100px);
    }

    ul {
      padding: 0;
      border: 1px solid # 000;
    }

    .box {
      position: relative;
      display: block;
      width: auto;
      background-color: #00f;
      color: #fff;
      margin: 5px;
      overflow: hidden;
    }

    .inner {
      position: relative;
      margin: 5px;
    }
  </style>
  <title>Performance optimization</title>
</head>

<body>
  <div class="animating">animation</div>
  <ul>
    <li class="box">
      <p class="inner">Ascend into a synthetic layer</p>
    </li>
    <li class="box">
      <p class="inner">Ascend into a synthetic layer</p>
    </li>
    <li class="box">
      <p class="inner">Ascend into a synthetic layer</p>
    </li>
    <li class="box">
      <p class="inner">Ascend into a synthetic layer</p>
    </li>
    <li class="box">
      <p class="inner">Ascend into a synthetic layer</p>
    </li>
  </ul>
</body>
</html>
Copy the code

When we mouse over the.animating element, we can clearly see the number of Composited Layers appearing by looking at the Layers panel.

This example does not appear to overlap; However, since it is likely to overlap with other elements when running the animation, the.animating element assumes that sibling elements are above a Composited Layer. In this case, the.box element sets overflow: hidden; This causes it to have a different Clipping Container from the.animating element, so layers explode.

The solution to this problem is simply to make the z-index of the.animating element higher than its siblings. Since the Composited Layer is on top of the normal elements, there is no need to upgrade the normal elements and fix the render order. As an aside, by default the Composited Layer renders order with a higher priority than normal elements; But set position: relative on the normal element; Later, the priority is higher than the Composited Layer because of the cascading context and after the document flow.

.animating { position: relative; z-index: 1; . }Copy the code

Of course, if sibling elements must be overlaid on top of the Composited Layer, overflow: hidden; Or the position: relative; Remove to optimize the number of Composited Layer creation or simply not create the Composited Layer.

The resources

  1. Wireless performance optimization: Composite
  2. Stick to synthesizer attributes and management counts only
  3. Simplify the complexity of drawing and reduce the drawing area
  4. CSS Animation performance optimization
  5. Use CSS3 will-change to improve page scrolling, animation and other rendering performance
  6. CSS3 hardware acceleration also has a pit
  7. In-depth understanding of cascading context and cascading order in CSS

conclusion

This article starts with some trees that need to be built for rendering, and then sorts out some optimizations based on how these trees relate to each part of the pipeline. For example, we optimized the composition through the Graphics Layer Tree.

Optimization should not be done blindly. For example, upgrading the ordinary Layer to Composite Layer will cause serious memory consumption if it is not used properly. Make good use of the Google Browser debug console to help us understand all aspects of the web in more detail. Thus targeted optimization of the web page.

The article draws on many sources, which are given at the end of each section. They are very valuable, and there are some details that may not have been sorted out in this article, so you can look at them for more insight.