浏览器的渲染原理
浏览器的渲染原理
- 掘金
整个过程
网络
- 网络线程: 收到html,css,js 文件资源. 产生一个渲染任务,并将其传递给渲染主线程的消息队列.
渲染
- 在事件循环机制下, 渲染主线程取出消息队列中的渲染任务,开启渲染流程.
整个过程
- 渲染主线程 完成 绘制之前的所有工作
- 合成线程和GPU 完成后续工作
第一步:解析HTML
1.1. 解析HTML字符串 => 生成 DOM 树 在控制台 直接打印 console.dir(document)
查看
// 等效于以下代码 这里就不会再发送网络请求了
function removeTag(htmlStr) {const parser = new DOMParser(); // 等效于const doc = parser.parseFromString(htmlStr, 'text/html');return doc;
}
1.2. 解析CSS字符串 => 生成 CSSOM树 (CSS object model)
- CSS解析不会阻塞HTML解析 :为了提高解析效率,浏览器会启动一个
预解析线程
率先下载和解析 CSS - 浏览器默认样式表: user agent stylesheet
- 控制台打印 CSSOM树(除了浏览器默认样式表的数据不能修改,其他都能修改)
- 包含1.浏览器默认样式, 2.内部样式,3.外部样式,4.行内样式
1.3 解析遇到JS则立即执行JS
- 渲染主线程遇到JS时 必须暂停一切行为,等待下载执行完成后才能继续预解析线程可以分担一点下载 JS 的任务. 这是因为JS的代码可能会修改当前的DOM树,所以DOM树的生成必须暂停.
第二步: 样式计算 - Recalculate Style
- 将第一步获得的 CSS树 和 DOM树 进行样式计算, 生成计算后的样式 = 最终样式 = 所有的CSS属性全部有值
- 在这个过程中,很多预设值会变成绝对值, 比如
red
会变成 rgb(255,0,0); 相对单位会变成绝对单位, 比如em
会变成px
最终得到一颗带有样式的 DOM 树 - 控制台查看计算后的样式
第三步: 布局 - Layout
- 布局阶段会依次遍历 DOM 树的每一个节点,计算每个节点的几何信息.例如节点的宽高,相对
包含块
的位置. 布局树
不一定和 DOM 树 一一对应 ,伪元素会在布局树, 但不在 DOM 树中, display: none 的 节点没有任何几何信息因此不在布局树中. 还有 匿名行盒,匿名块盒等等都会导致DOM树和布局树无法一一对应.
第四步: 分层 - Layer
- 滚动条单独分层
第五步: 绘制 - Paint
- 为每一层生成如何绘制的指令
- 类似于canvas
渲染主线程
的工作到此为止,剩余步骤交给其他线程完成
第六步: 分块 - Tiling
- 分块会将每一层分为多个小的区域
- 这一步是
合成线程
完成. - 合成线程首先对每个图层进行分块,将其划分为更多的小区域…
- 它会从
线程池
中拿取多个线程来完成分块工作.
第七步: 光栅化 - Raster
- 光栅化是将每个块变成位图
- 优先处理靠近视口的块
- 此过程会用到 GPU 加速(GPU 进程)
第八步: 画 - Draw
- 合成线程计算出每个位图在屏幕上的位置, 交给GPU 进行最终呈现
常见面试题
- 什么是 reflow(重排)?
- reflow 的本质就是
重新计算
layout 树。 - 当进行了会影响布局树的操作后,需要重新计算布局树,会引发 layout。
- 为了避免连续的多次操作导致布局树反复计算,浏览器会合并这些操作,当 JS 代码全部完成后再进行统一计算。所以,改动属性造成的 reflow 是
异步
完成的。 - 也同样因为如此,当 JS 获取布局属性时,就可能造成无法获取到最新的布局信息。
- 浏览器在反复权衡下,最终决定
读取
属性立即 reflow。
- reflow 的本质就是
- 什么是 repaint(重绘)?
- repaint 的本质就是重新根据分层信息计算了绘制指令。
- 当改动了可见样式后,就需要重新计算,会引发 repaint。
- 由于元素的布局信息也属于可见样式,所以 reflow 一定会
引起
repaint。
- 为什么 transform 的效率高?
- 因为 transform 既不会影响布局也不会影响绘制指令,它影响的只是渲染流程的最后一个draw阶段
- 由于 draw 阶段在合成线程中,所以 transform 的变化几乎不会影响渲染主线程。反之,渲染主线程无论如何忙碌,也不会影响 transform 的变化。