Page rendering has the following characteristics:

  • Single threaded event polling
  • Well-defined, continuous, and orderly (HTML5)
  • Word segmentation and DOM tree building
  • Request resources and preload them
  • Build the render tree and draw the page

To be specific:

When we get the corresponding bytes of HTML from the network, the DOM tree is built. The browser’s UI update thread is responsible. DOM tree construction is blocked when:

  • The HTML response stream is blocked in the network
  • There is an unfinished script
  • A Script node is encountered, but there is an unfinished style file

The render tree is built from a DOM tree and is blocked by a style file. Because of single-threaded event polling, even without script or style blocking, the rendering of the page will be blocked when these scripts or styles are parsed, executed, and applied.

Some situations that don’t block page rendering:

  • Defined for the defer and async properties

    • Defer is equivalent to window.onLoad. Once the DOM of the page has been parsed, execute the contents of the JS file
  • No style file for matching media type

  • No script node or style node is inserted through the parser

1 < HTML > 2 <body> 3 <link rel="stylesheet" href="a.css"> 4 <div>Hi there! </div> 5 <script> 6 document.write('<script SRC ="other.js"></ SCR '+ 'ipt>'); 7 </script> 8 <div>Hi again! </div> 9 <script SRC ="last.js"></script> 10 </body> 11 </ HTML >Copy the code

Let’s go back in slow motion and see how it actually rendered

< HTML > 2 <body> 3 <link rel="stylesheet" href="a.css"> 4 <div>Hi there! </div> 5 <script>Copy the code

First, the parser encounters A.css and downloads it from the network. The process of downloading the stylesheet is time consuming, but the parser is not blocked and continues parsing. Next, the parser encounters the script tag, but blocks execution of the script because the style file is not loaded. The parser is blocked and cannot parse further.

The render tree is also blocked by style files, so no browser will render the page at this point.

Moving on…

< HTML > <body> <link rel="stylesheet" href="a.css"> <div>Hi there! < / div > < script > document. Write (' < script SRC = "other. Js" > < / SCR ipt > '+', '); </script>Copy the code

Once the A.css file is loaded, the render tree is built.

As soon as the script is executed inline, the parser is blocked by other.js. Once the parser is blocked, the browser receives a draw request, “Hi there!” It’s displayed on the page.

When other.js is loaded, the parser continues to parse…

< HTML > 2 <body> 3 <link rel="stylesheet" href="a.css"> 4 <div>Hi there! </div> 5 <script> 6 document.write('<script SRC ="other.js"></ SCR '+ 'ipt>'); 7 </script> 8 <div>Hi again! 9 < / div > < script SRC = "last. Js" > < / script >Copy the code

The parser blocks when it encounters Last.js, and the browser receives another draw request, “Hi again!” It shows up on the page. Finally, last.js will be loaded and executed.

However, modern browsers use speculative loading to ease rendering blocking.

In this case, scripts and style files can severely block the rendering of the page. The purpose of preloading is to reduce this blocking time. When a render is blocked, it does the following:

  • Lightweight HTML (or CSS) scanners continue to scan through documents
  • Look up the URLS of resource files that you might need in the future
  • Download them before the renderer uses them

Go back to the example above and guess how preloading works.

< HTML > 2 <body> 3 <link rel="stylesheet" href="a.css"> 4 <div>Hi there! 5 < script > < / div >Copy the code

The parser returns A.css and retrieves it from the network. The parser does not block and continues parsing. When it encounters an inline script node, it blocks, blocking the execution of the script because the style file has not been loaded. The render tree is also blocked by the style file, so the browser doesn’t receive the render request and can’t see anything. So far, in the same way that I just mentioned. But then something changed.

The preloader continues to “read” the document and Speculative, finds Last.js, and attempts to load it. (Note: At this point, Last.js is not loaded if A.css is not loaded, and is in a pending state.) The following:

< HTML > 2 <body> 3 <link rel="stylesheet" href="a.css"> 4 <div>Hi there! </div> 5 <script> 6 document.write('<script SRC ="other.js"></ SCR '+ 'ipt>'); 7 < / script >Copy the code

Once the A.css file is loaded, the render tree is built, the inline script can be executed, and then the parser is blocked by other.js. After the parser is blocked, the browser receives the first render request, “Hi there!” Will be reality on the page. This step is the same as the previous one. And then:

< HTML > 2 <body> 3 <link rel="stylesheet" href="a.css"> 4 <div>Hi there! </div> 5 <script> 6 document.write('<script SRC ="other.js"></ SCR '+ 'ipt>'); 7 </script> 8 <div>Hi again! 9 < / div > < script SRC = "last. Js" > < / script >Copy the code

The parser finds Last.js, but since the preloader has just loaded it into the browser cache, last.js will be executed immediately. After that, the browser receives a render request and “Hi again” is displayed on the page

Rearrange, redraw,

Rearranging is literally rearranging, and redrawing is redrawing. And obviously if you rearrange it, you have to redraw it. Rearrangement and redrawing are based on dom elements, right? Well, not really. It’s the render Tree node

HTML is built into a DOM tree (DOM = Document Object Model). The DOM tree construction process is a deep traversal process: the next sibling node of the current node will be built only after all the child nodes of the current node have been built.

Parse CSS into CSS to construct a CSSOM tree (CSSOM = CSS Object Model)

Create Rendering Tree based on DOM Tree and CSSOM. Note: Rendering Tree is not the same as DOM Tree, because something like display: None doesn’t need to be in the Rendering Tree.

  • The “display: None” element removes the node from the entire Render tree, so it is not part of the layout

With Render Tree, browsers already know what nodes are in a web page, their CSS definitions, and their dependencies.

The Layout render tree calculates the position of each node on the screen.

The next step is to draw, which is to traverse the Render tree and draw each node using the browser UI back-end layer.

Javascript may change the structure of the DOM tree and csSOM during the whole process, so browsers tend to wait for JS to load, thus shelving the entire render count build.

The layout and drawing sequence can be different for different browsers. Based on different browser optimization strategies, browsers can better coordinate the layout and drawing process.

And when we say redraw and rearrange, it’s the process of rearranging and redrawing.

Changes to the render tree cause rearrangements, while attribute changes to nodes of the render number cause redraws.

Rearrangement: DOM changes affect the width and height of elements, causing browsers to recalculate the width and height of elements (affecting the layout of the page) and even re-render parts of the render count. Changes in window size, text size, content changes, browser window size, style attribute changes, and so on cause rearrangements. Rearrangement must lead to redrawing. Redrawing does not necessarily lead to rearrangement. Redraw: The appearance of an element has been changed without changing its width and height, such as changing its background color, outline, visibility, etc., resulting in redrawing

Rearrangement must affect redrawing, but redrawing need not cause rearrangement

To optimize the

  1. Browser optimizations: The browser maintains a queue, places all operations that cause backflow and redraw in this queue, and when the number of operations in the queue reaches a certain number or interval, the browser flushes the queue and performs a batch. This will turn multiple backflow and redraw into a single backflow redraw.

  2. Optimizations to note: We want to reduce redrawing and rearrangement to reduce rendering tree operations, so we can incorporate multiple DOM and style changes. And reduce the need for style styles.

    • Change the className of the element directly
    • Display: none; Set the element to display: None; Then the page layout and other operations; Set the element to display: block; This only causes two redraws and rearrangements;
    • Do not access the browser’s Flush queue attribute too often; If you must access, you can take advantage of caching. Store accessed values so that subsequent use does not cause backflow;
    For example, the myElement moves diagonally, one pixel at a time. End at 500 by 500 pixels. left = 1 + myelement.offsetLeft + 'px'; = 1 + myElement.offsetTop + 'px'; if(myElement.offsetLeft >= 500){ stopAnimation(); } // This method is obviously inefficient, as offsets are queried every time a move is made, causing the browser to refresh the render queue to the detriment of optimization. A good idea is to get the value of the starting position once and then assign it to a variable. Var current = myelement. offsetLeft; current++; = current + 'px'; = current + 'px'; if(myElement.offsetLeft >= 500){ stopAnimation(); }Copy the code
    • Use cloneNode(True or False) and replaceChild techniques to cause a backflow and redraw
    • Set position to absolute or fixed for elements that need to be rearranged multiple times. The element is removed from the document flow and its changes do not affect other elements.
    • If multiple DOM nodes need to be created, you can use the DocumentFragment to add the Document once after creation.
    • Try not to use a table layout.


From the above process, we know that dom nodes are rendered as they are loaded. CSS resources and JS resources are also rendered at the same time as the document is loaded.

This leads to some blocking problems, where the user sees a blank screen. For example, the browser does not know what scripts are executed in xx.js and how they will affect the page, so the browser will wait for the js file to download and execute before continuing rendering. If this takes too long, the screen will go blank.

If a dom node is involved in resource loading, dom rendering will be affected. Things like JS loading and execution affect dom tree parsing. CSS loading affects csSOM formation, but does not affect DOM tree formation. So js resources are put at the end of the document and styles are put at the front.

Whether it is JS resources or CSS resources, we are required to be as compact and small as possible, the same reason.

CSS blocks DOM rendering

Both external and inline CSS block DOM Rendering, while DOM Parsing proceeds normally. This means that the HTML after the CSS is not displayed until it has been downloaded and parsed. This is why we place styles before HTML content to prevent style jumping in rendered content. The trade-off, of course, is display latency, so performance-critical sites have all CSS inlined. CSS can be downloaded in parallel, under what circumstances can block loading occur (in test observation, CSS in IE6 is blocked loading)

When a CSS is followed by an embedded JS, the CSS will block subsequent resource downloads. When you put embedded JS in front of CSS, there is no blocking.

Root cause: Because browsers maintain the order of CSS and JS in HTML, stylesheets must be loaded and parsed before the embedded JS executes. The embedded JS will block subsequent resource loading, so the CSS above will block the download.

Where should embedded JS be placed? 1. Placing it at the bottom will still block all rendering, but not resource downloads. If the embedded JS is in the head, put the embedded JS in the CSS header. 4. Do not call long-running functions from embedded JS. If you must, use setTimeout instead

JS blocks DOM parsing

Either inline or external-chained JavaScript blocks subsequent DOM Parsing, as well as subsequent DOM Rendering. This means that elements inserted during script execution will be rendered before subsequent HTML, even if the script is an external resource. Since JavaScript blocks only subsequent DOM, the previous DOM is rendered to the user immediately after parsing is complete. That’s why we put the script at the bottom of the page: the page should display normally while the script is still downloading.

The solution

  1. Lazy loading

    If the initial rendering of the page does not depend on JS or CSS, you can use deferred loading, which means loading the JS and CSS last, and writing the code referencing external files last. For example, some button click events, such as the rotation of the animation script can also be placed at the end.

  2. Defer deferred loading

    The parsing starts when the document is parsed, and before the events are completed, the parses are downloaded in the order they appear in the document. The effect is the same as before placing the script at the end of the document.

  3. Asynchronous loading

    This tells the browser that it doesn’t have to wait until the external file is loaded, that it can be downloaded while rendering and executed when it is finished.