将 HTML 字符串通过一连串的规则得到像素信息。
整体框架:
当浏览器的网络线程收到 HTML 文档后,会产生一个渲染任务,并将其传递给渲染主线程的消息队列。
在[[事件循环]]机制的作用下,渲染主线程取出消息队列中的渲染任务,开启渲染流程。
将 HTML 进行解析,生成 DOM 树、CSSOM 树。
从上到下解析 HTML 文档,遇到 CSS 解析 CSS,遇到 JS 根据规则执行 JS。
StyleSheetList 存储当前页面所有样式表的集合,样式表分为:
<Style>
内部样式表<Link ...>
外部样式表<div style="..."
行内样式表为了提高解析效率,浏览器会启动一个预下载线程,快速浏览,预解析 CSS。CSSOM 的生成还是需要依靠渲染主线程。
如果主线程解析到 Link 位置,此时外部的 CSS 还没有下载、预解析好,主线程不会等待,继续解析后续的 HTML。因为下载和预解析 CSS 的工作是在预下载线程进行的,不会阻塞 HTML 解析。
判断 <script>
的属性中是否包含async
或defer
,根据不同的规则进行解析:
如果没有以上两个属性,渲染主线程解析 HTML 时遇到 JS 则会阻塞,必须暂停⼀切⾏为,等待下载并执⾏完脚本后才能继续解析 HTML。
JS 代码的执行过程可能会修改当前的 DOM 树,所以 DOM 树会暂停。
将 DOM 树和 CSSOM 树进行样式计算,得到每个节点最终的计算样式,生成一颗带样式的 DOM 树,即渲染树。
主线程会遍历得到的 DOM 树,依次为树中的每个节点计算出它最终的样式。
在这一过程中,预设值会变成绝对值,比如 red
会变成 rgb(255, 0, 0)
;相对单位会变成绝对单位,比如 em
会变成 px
。
并且一些节点会被忽略。如一些节点会使用 CSS(例如 display: "none"
属性)隐藏,它们将被忽略。 如 script
或 meta
标签不可见(这些标签在浏览器默认样式表中为display: "none"
),会被忽略。
渲染树保存有关哪些节点显示以及它们计算出的样式的信息,但不包括每个节点的尺寸或位置。
在布局阶段,基于渲染树,计算这些节点在设备视口的确切位置及其大小,例如节点的宽高、相对包含快的位置。
布局或回流(reflow)并非仅发生一次,而是每当我们在 DOM 中更改影响页面布局(即使是部分影响)的任何内容时都会发生。元素位置被重新计算的情况示例有:
主线程会使用一套策略对整个布局树进行分层。
分层的好处在于,将来某一个层改变后,仅会对该层进行后续处理,从而提升效率。
如果我们想要暗示浏览器某些元素应该位于单独的层上,我们可以使用 will-change
CSS 属性。有一些特定的属性和元素表示创建了一个新层。其中一些是 <video>
、 <canvas>
、 opacity
CSS 属性、一个 3D transform
、 will-change
等等。这些节点及其后代将绘制到它们自己的层上。
为每一层生成如何绘制的指令。
渲染主线程的工作到此为止,剩余步骤交给其他线程完成。
分块将每一层分为多个小的区域。
分块的⼯作是交给多个线程同时进⾏的。
光栅化是将每个块变成位图,优先处理靠近视口的块。
此过程会用到 GPU 加速:
合成线程计算出每个位图在屏幕上的位置,交给 GPU 进⾏最终呈现。
完整流程:
Reflow 的本质就是重新计算 Layout 树。
当做出一些操作,影响页面几何布局,引发一系列的重新计算:CSSOM 树改变,Render 树更新,Layout 树更新,重新绘制。
Repaint 的本质就是重新根据分层信息,计算新的绘制指令,进行绘制。
当改动了可见样式后,就需要重新计算,会引发重绘。
因为 transform 既不影响布局也不影响绘制,它影响的只是渲染流程的最后一个「draw」阶段(和 animation 配合使用)。
由于 draw 阶段在合成线程中,所以 tranform 的变化几乎不会影响渲染主线程。反之,渲染主线程如何阻塞,也不会影响 transform 的变化。