写在前面

浏览器在从服务器端获得 html、css、js 文件后要进行页面的渲染,浏览器中有两个引擎非常重要,一个是渲染引擎,负责 html 和 css 文件的解析和渲染,另个一是 js 引擎,负责处理 js 文件逻辑。

浏览器渲染页面的整个流程到底是怎么样的呢?

DOM 操作是跨线程的操作。默认情况下,js 线程和 渲染线程是相互独立地,如果 js 想做一些更改页面信息的操作,需要通过 DOM 调用的方式让渲染线程执行渲染操作。因此 DOM 操作是会引起浏览器重新渲染的。最典型的就是在 js 中使用 DOM 的 dom.clientWidth,可能什么都不做,但单单访问就会触发渲染。 因此应该避免频繁的 DOM 操作。 为了优化性能,DOM 操作可能会在不同的浏览器中被合并到一起执行,除非中间间隔使用 dom.clientWidth。因此为了避免多次渲染,应该将多次操作合并到一起,避免中间使用特殊的引发 rerender 的属性。

渲染流程

1. 根据HTML构建HTML树(DOM)

首先,浏览器会根据 HTML 文件进行 DOM 树的构建,以数据结构中树的概念来描述各 DOM 节点间的关系。

2. 根据CSS构建CSS树(CSSDOM)

同样的,浏览器也会根据 CSS 文件来构建一颗 CSS DOM 树,这颗树可能比 HTML 树要小一些,因为一般 head 标签节点都没有样式。

3. 将两棵树合成一棵渲染树(render tree)

在 HTML树 和 CSS树 构建完成后,浏览器会将这两棵树进行合并,合并成一个带有样式的 DOM 树,就是最终的渲染树,浏览器会根据渲染树进行页面最终的渲染。

4. layout确定布局定位

layout 布局会根据渲染树对树中节点进行轮廓的计算,根据文档流、盒模型、计算大小和位置等等,来确定各DOM节点的大致位置,类似于美术考试中给定一个场景,开始用速写绘制出其大致轮廓。

5. Paint绘制上色

Paint 绘制是会对页面元素进行更细致的绘制,把页面元素的边框颜色、文字颜色、阴影等绘制出来,类似于美术中速写绘制完轮廓后开始填色。

6. 根据层叠上下文进行Composite合成

浏览器中 DOM 元素之间是有层叠关系的,在每一层都完成上色后,需要将各层叠元素之间进行合成拍平,以便于最终更方便的渲染页面,防止浏览器渲染时渲染出一些被遮挡的没必要的部分。

Reflow 和 Repaint

上述浏览器的渲染方式是从头开始的完整渲染方式,但是我们知道,js 也可以操作 DOM 进行 DOM 布局和样式的改变,那么 js 操作 DOM 改变了 style 后肯定要再次进行渲染,但这次的渲染就不再是从头开始构建 DOM 树了,而是在已有 DOM 树的基础上进行局部的渲染。

比如 js 添加或删除了 DOM ,那么就会引起整个页面中局部布局的改变,因此,浏览器会从 layout -> Paint -> Composite 重新走一遍。这就是 Reflow ,被翻译成 回流 或者 重排

如果 js 只改变了 DOM 的背景色或边框色等,不会引起页面布局的改变,因此,浏览器会从 Paint -> Composite 走一遍。这就是 Repait,也就是 重绘

如果 js 只是改变了 DOM 元素的 transform 等,既不会引起布局的改变,又不会引起色彩的改变,浏览器就只会重新执行 Composite 。

不同 css 属性的改变会触发什么渲染流程呢?这个只能一一地试了,但是有程序员试过后总结了下来,具体可访问 CSS Triggers

回流(重排)和重绘相比起来肯定是回流(重排)的代价比较大。

哪些操作会导致回流呢?

  1. 删除和添加元素
  2. display: none 隐藏显示元素
  3. 浏览器大小改变,触发 resize
  4. 用 js 获取某些属性,例如最常见的 getBoundingClientRect,还有 widthclientWidthoffsetWidthscrollWidth等等。

那么如何避免回流呢?

  1. 避免频繁操作 dom
  2. 避免多次读取 widthoffsetLeft等属性。
  3. 避免逐项更改样式,最好多次更改合并到一次
  4. 对于复杂元素使用绝对定位,使其脱离文档流。

    参考链接

    渲染树构建、布局及绘制
    渲染性能
    坚持仅合成器的属性和管理层计数