回流与重绘

浏览器渲染的关键路径

The critical rendering path includes the Document Object Model (DOM), CSS Object Model (CSSOM), render tree and layout.

关于渲染树

The render tree only captures visible content. The head section (generally) doesn’t contain any visible information, and is therefore not included in the render tree. If there’s a display: none; set on an element, neither it, nor any of its descendants, are in the render tree.

To construct the render tree, the browser roughly does the following:

  1. Starting at the root of the DOM tree, traverse each visible node.
    • Some nodes are not visible (for example, script tags, meta tags, and so on), and are omitted since they are not reflected in the rendered output.
    • Some nodes are hidden via CSS and are also omitted from the render tree; for example, the span node—in the example above—is missing from the render tree because we have an explicit rule that sets the “display: none” property on it.
  2. For each visible node, find the appropriate matching CSSOM rules and apply them.
  3. Emit visible nodes with content and their computed styles.
1
2
3
note that visibility: hidden is different from display: none. 
The former makes the element invisible, but the element still occupies space in the layout (that is, it's rendered as an empty box),
whereas the latter (display: none) removes the element entirely from the render tree such that the element is invisible and is not part of the layout.

补充

生成“布局”(layout)和”绘制”(paint)这两步,合称为”渲染”(render)。

聊到渲染我又想写写CSS的渲染阻塞,CSS不会阻塞Dom解析,但是会阻塞Dom渲染,阻塞JS的执行,有同学可能会有JS,CSS下载时间的疑惑,这就不得不提预加载扫描器(Preload Scanner)。

这是MDN的原话:

While the browser builds the DOM tree, this process occupies the main thread. As this happens, the preload scanner will parse through the content available and request high priority resources like CSS, JavaScript, and web fonts. Thanks to the preload scanner, we don’t have to wait until the parser finds a reference to an external resource to request it. It will retrieve resources in the background so that by the time the main HTML parser reaches requested assets, they may possibly already be in flight, or have been downloaded. The optimizations the preload scanner provides reduce blockages.

1
2
3
4
<link rel="stylesheet" src="styles.css"/>
<script src="myscript.js" async></script>
<img src="myimage.jpg" alt="image description"/>
<script src="anotherscript.js" async></script>

In this example, while the main thread is parsing the HTML and CSS, the preload scanner will find the scripts and image, and start downloading them as well. To ensure the script doesn’t block the process, add the async attribute, or the defer attribute if JavaScript parsing and execution order is not important.

Waiting to obtain CSS doesn’t block HTML parsing or downloading, but it does block JavaScript, because JavaScript is often used to query CSS properties’ impact on elements.

布局与绘制定义

布局

Once the render tree is built, layout becomes possible. Layout is dependent on the size of screen. The layout step determines where and how the elements are positioned on the page, determining the width and height of each element, and where they are in relation to each other.

绘制

The last step is painting the pixels to the screen. Once the render tree is created and layout occurs, the pixels can be painted to the screen. Onload, the entire screen is painted. After that, only impacted areas of the screen will be repainted, as browsers are optimized to repaint the minimum area required. Paint time depends on what kind of updates are being applied to the render tree.

布局触发条件

Any time the render tree is modified, such as by added nodes, altered content, or updated box model styles on a node, layout occurs.

关于绘制

While painting is a very fast process, and therefore likely not the most impactful place to focus on in improving performance。

浏览器优化机制

现代的浏览器都是很聪明的,由于每次重排都会造成额外的计算消耗,因此大多数浏览器都会通过队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列。但是!当你获取布局信息的操作的时候,会强制队列刷新,比如当你访问以下属性或者使用以下方法:

  • offsetTop、offsetLeft、offsetWidth、offsetHeight
  • scrollTop、scrollLeft、scrollWidth、scrollHeight
  • clientTop、clientLeft、clientWidth、clientHeight
  • getComputedStyle()
  • getBoundingClientRect
  • 具体可以访问这个网站:https://gist.github.com/pauli…点击预览

以上属性和方法都需要返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来返回正确的值。因此,我们在修改样式的时候,最好避免使用上面列出的属性,他们都会刷新渲染队列。如果要使用它们,最好将值缓存起来。

如何减少回流与重绘带来的性能损失

  • 先将元素设为display: none(需要1次重排和重绘),然后对这个节点进行100次操作,最后再恢复显示(需要1次重排和重绘)。这样一来,你就用两次重新渲染,取代了可能高达100次的重新渲染。
  • position属性为absolutefixed的元素,重排的开销会比较小,因为不用考虑它对其他元素的影响。

参考链接:

https://segmentfault.com/a/1190000017329980

https://www.ruanyifeng.com/blog/2015/09/web-page-performance-in-depth.html

https://developer.mozilla.org/en-US/docs/Web/Performance/Critical_rendering_path

https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction

https://developer.mozilla.org/zh-CN/docs/Web/Performance/How_browsers_work