Original address: waynegong.cn/posts/49203…

TLDR:

Both CSS and JS block the key render path of the page, with different blocking effects:

  • Execution of inline JS blocks a DOM Layout;
  • Loading and executing external JS blocks DOM builds;
  • The CSSOM build of an inline CSS blocks the build of the render tree, thus blocking the Layout;
  • Both the loading of external CSS and the CSSOM build block the construction of the render tree, thus blocking the layout;
  • In the case of a page with CSS and JS, the CSSOM build blocks subsequent JS execution;
  • scriptOf the labelasyncderferProperty to enable JS loading without blocking DOM building;

Test the Demo

To verify how critical render paths are blocked, run a Web Server and watch the page render process through a browser debugging tool (Chrome DevTools — Performance).

A simple Demo

How does JS block page rendering

Since JS may modify the DOM, running JS and building the DOM cannot be done at the same time, so JS has a big impact on page rendering.

Verifying the impact of Js on page rendering can be divided into three cases:

  1. Inline JS
  2. JS introduced by external links
  3. External links and JS using the async, defer properties

Effects of inline JS on page rendering

Demo /static/index.html:

<html>
<body>
  <h1>hello</h1>
  <script>
    const stopTime = Date.now() + 20
    while (Date.now() < stopTime);
  </script>
  <br>
  <h1>world</h1>
</body>
</html>
Copy the code

The results are as follows:

As soon as the browser receives the HTML content, it starts parsing HTML (building the DOM). Click Parse HTML to see the detailed execution time. As follows:

Parse HTML (building the DOM) took only 0.3ms, while JS blocked 19.9ms, and the entire Parse HTML process took about 20.1 ms.

Given that JS code takes longer to execute, Parse HTML will be stretched longer and unable to move to the next Layout.

It can be concluded that execution of inline JS blocks critical render paths.

External chain JS effect on page rendering

Demo /static/index.html:

<html> <body> <h1>hello</h1> <script src="/block.js? t=100"></script> <br> <h1>world</h1> </body> </html>Copy the code

The results are as follows:

Unlike inline JS, Parse HTML is split twice.

In the first Parse HTML, the browser parses the contents (lines 0-3) before the script tag in index. HTML and immediately requests block.js, stopping parsing.

While waiting for block.js to load, the browser does not parse the content behind the script tag. Instead, it lays out and paints the content that has already been parsed. The first drawing (FP) takes place before the block.js load is complete, but only Hello is displayed on the page and the content is incomplete.

When block.js is loaded, the browser executes the JS code first, rather than parsing the HTML that follows. The rest of the content is parsed, redrawn, and redrawn until the code is executed.

In the process of waiting for the external JS loading to complete, the browser cannot parse the content behind the script tag, that is, JS loading will block DOM construction. After the external JS is loaded, JS execution is required, which also blocks DOM construction.

It can be concluded that the loading and execution of JS introduced by the external chain will block the critical render path.

Effects of async and Derfer properties on page rendering

MDN’s interpretation of these two attributes:

Async: For normal scripts, if the async property is present, the normal script is requested in parallel and parsed and executed as soon as possible.

Defer: This Boolean property is set to inform the browser that the script will be executed after the document has been parsed and before the DOMContentLoaded event is triggered. Scripts with the defer attribute block the DOMContentLoaded event until the script is loaded and parsed.

Demo /static/index.html:

<html> <body> <h1>hello</h1> <script async src="/block.js? t=100"></script> <br> <h1>world</h1> </body> </html>Copy the code

The async property is used as follows:

The defer attribute looks like this:

As you can see, with the async and defer properties, the loading of block.js does not affect the Parse HTML.

As soon as the browser receives the HTML, it starts parsing the page, and when it encounters a script tag, it immediately issues a request to load block.js.

Instead, the browser continues to parse, lay out, and draw the content behind the script.

It can be concluded that the async and defer properties both allow the JS introduced by the outer chain to not block Parse HTML (DOM build).

How do CSS resources block page rendering

Unlike JS, CSS does not modify the DOM and therefore does not block building the DOM, so building the DOM and building CSSOM are in perfect harmony.

The inline CSS is already returned in HTML, so it has no impact on the DOM build.

Demo /static/index.html:

The results are as follows:

The browser needs to merge the DOM and CSSOM into a Render tree to layout and draw.

If CSS is introduced via an external chain, even though CSS does not block DOM builds, the CSSOM build cannot be done until the CSS is loaded and the render tree cannot be built, thus blocking the critical rendering process.

As you can see, after the Parse HTML is complete, you wait for block. CSS to load, and then build, render, and draw the CSSOM tree.

Prove that CSS causes critical render paths to block.

When JS meets CSS

The above case only discussed HTML + JS, HTML + CSS case, if HTML, JS, CSS exist at the same time, what will be the impact on the page rendering?

JS blocks DOM builds because JS may modify the DOM, so it can only be executed sequentially.

In the same way, JS styles can change, so JS cannot be executed until the CSSOM is built, which means that CSSOM blocks JS execution (in the case of JS after CSS).

To recap the relationship from the beginning: CSS blocks the construction of the render tree and the subsequent JS execution, and THE JS execution and DOM construction block each other.

Demo /static/index.html:

<html> <body> <h1>hello</h1> <script src="/block.js? t=100"></script> <h1>world</h1> <link rel="stylesheet" href="/block.css? t=300"> <script src="/block.js? t=200"></script> </body> </html>Copy the code

The results are as follows:

Can be found even if block.js? T = 200 – block. CSS? T =300 is loaded first, but instead of executing immediately, it waits for block. CSS? T =300. Run this command only after the loading is complete.

Why does CSS need to be in the header

CSS does not block DOM builds, but it does block render tree builds, blocking layouts that affect key rendering flows.

If you place CSS in the middle or at the bottom of the page, CSS does not block DOM construction and parsed content is rendered.

The page will be re-rendered once the CSS has loaded, possibly causing the page to change. On the one hand, unnecessary re-rendering creates an additional performance burden, and on the other hand, the experience of changing pages is very bad.

So put important CSS resources in the header of the page and start loading as early as possible to reduce the blocking time on network requests and avoid page blocking to improve rendering efficiency.

In addition, you are advised to load large CSS resources through external links to make full use of the cache and reduce the request time.

Why does JS need to be at the bottom of the page

The script tag blocks DOM builds without setting async and defer properties.

If such a script tag is placed in the header of the page, the HTML content behind the script will not be parsed until the loading is complete, and the page will remain blank until the JS loading execution is complete, which will be a very bad experience.

Putting JS at the bottom of the page doesn’t have this problem. Even if the browser parses the HTML before blocking the script tag at the bottom, the content will still be displayed and the user will still see the full page in the first place.

However, with async and derfer properties, the position of the script tag is not so important.

Modern browsers also do a lot of work on page parsing and rendering, and optimization techniques such as preloading scanners have greatly improved rendering efficiency.