浏览器渲染原理
想要理解reFlow和rePaint,就需要先明白浏览器渲染一个html字符串文档的流程。打开一个浏览器标签页,地址栏输入地址回车之后,浏览器进程会开启一个网络进程,获取对应资源,获取到之后会开启一个对应的渲染进程,渲染进程会开启渲染主进程,此时能拿到网络返回的HTML字符串文本,接下来开始浏览器渲染的8个大步骤。
- 解析HTML,首先解析结果会是一个DOM树和一个CSS Object Model树。
- 解析过程中遇到 CSS 解析 CSS,遇到 JS 执行 JS。为了提高解析效率,浏览器在开始解析前,会启动一个预解析的线程,优先下载外部 CSS 文件和 外部的 JS 文件。
- 如果渲染主线程解析到
link
位置,此时外部的 CSS 文件还没有下载解析好,渲染主线程不会等待,继续解析后续的 HTML。这是因为下载和解析 CSS 的工作是在预解析线程中进行的。这就是 CSS 不会阻塞 HTML 解析的根本原因。 - 如果主线程解析到
script
位置,会停止解析 HTML,转而等待 JS 文件下载好,并将js文件中的全局代码解析执行完成后,才能继续解析 HTML。这是因为 JS 代码的执行过程可能会修改当前的 DOM 节点,所以 DOM 树的生成必须暂停。这就是 JS 会阻塞 HTML 解析的根本原因。 - 第一步完成后,会得到 DOM 树和 CSSOM 树,浏览器的默认样式、内部样式、外部样式、行内样式均会包含在 CSSOM 树中。
- 样式计算(Computed Style),主线程会遍历 DOM 树,依次为树中的每个节点计算出它最终的样式,称之为 Computed Style。在过程中,很多预设值会变成绝对值,比如
red
会变成rgb
,相对单位rem、em
会变成绝对单位px
,可以在浏览器检查元素的computed卡片查看,可以看到每个元素都会有完整的全部样式属性。这一步完成后,会得到一棵带有样式的 DOM 树。 - 对样式计算结果进行处理,生成Layout树,称为布局。layout树包括元素的尺寸、位置信息,处理过程中如display:none隐藏节点不会生成到layout树上,:before这些伪类元素在DOM树上不存在,但是会在Layout树上,所以DOM树和Layout树不一定是一一对应的。
- 分层(Layer),如果一个页面很大,每次有更新时重新布局整个页面太过耗时,所以浏览器会有一套算法自动进行分层,主要作用就是把页面拆解成不同的图层,当页面在视觉上发生变化时,这些变化其实只会影响某一个图层,其他图层不受影响,更高效的完成绘制。
- 拆分图层默认情况下是浏览器决定的,浏览器主要分析元素之间是否互相影响,如果某些元素对其他元素造成的影响很大,就会被浏览器提取成单独的图层。
- 我们也可以主动的把某些元素提取到单独的图层,但是也不是拆分越多越好,因为图层越多开销也越高,从而适得其反影响网页的性能。因此只把特定的,能达到效果的元素提到一个图层中。
- 滚动条、堆叠上下文、transform、opacity 等样式都会影响分层结果,在元素上设置willChange属性可以主动分层,值为’transform’表示提示浏览器后续这个元素的transform属性会频繁变动,让浏览器自动分层。
- 绘制(Paint),为每一层生成如何进行绘制的指令集,类似canvas,输入指令 生成结果,事实上canvas就利用了浏览器内核的绘制核心。
---- 到此为止,渲染主进程工作暂时结束,剩下的步骤交给其他进程完成。
- 合成线程-分块(Tiling),之前分的图层也很大,浏览器会对图层进行分块,将每一层分为多个小的区域。分块的工作是同时交给多个线程同时进行的。
- 合成线程-光栅化(Raster),光栅化是将每一个块变成位图,位图信息包括位置、尺寸、颜色等。优先处理靠近视口的块。过程中会用到GPU进行加速,提升光栅化速度。
- 画(Draw),合成线程计算出每个位图在屏幕的位置,交给GPU进行最终呈现。
reFlow重新布局
reFlow 的本质就是当进行了会影响布局树的操作后,比如修改元素位置(offsetWidth等)、尺寸(宽高)、字体大小等,需要重新计算布局树Layout。
浏览器为了避免连续的多次操作导致布局树反复计算,比如第一行修改了元素宽度,第二行修改了元素高度,第三行修改了其他尺寸,浏览器会合并这些操作,并放到消息队列,等 JS 代码全部完成后再进行统一计算。所以,改动属性造成的 reFlow 是异步完成的。
但是代码书写过程中,我修改完可能需要立即获取当前的最新宽度,此时当 JS 获取布局属性时,就可能造成无法获取到最新的布局信息,浏览器在反复权衡下,最终决定当有获取属性的代码时立即 reFlow。
rePaint重绘
repaint 的本质就是重新根据分层信息计算了绘制指令。当改动了可见样式后,比如颜色,就需要重新绘制像素信息,颜色跟尺寸位置无关,不需要重新 生成Layout树,所以会重新计算绘制指令,引发 rePaint。
由于元素的布局信息也属于可见样式,所以 reFlow 一定会引起 rePaint。
扩展信息
为什么 transform 的效率高?
因为 transform 既不会影响布局Layout树也不会影响Paint绘制指令,它影响的只是渲染流程的最后一个「Draw」阶段,一般在GPU中处理,速度很快。由于 draw 阶段在合成线程中,所以 transform 的变化几乎不会影响渲染主线程。反之,渲染主线程无论如何忙碌,也不会影响 transform 的变化。