1. 概念
概念/术语 | 定义 |
---|---|
DOM (文档对象模型) | HTML文档的内部树状结构,表示元素及其关系 |
CSSOM (CSS对象模型) | CSS规则的结构化表示 |
计算样式 (Computed Style) | 每个节点的最终样式 |
布局树 (Layout Tree) | 基于DOM和样式计算出的视觉结构,用于确定位置和大小。 |
绘制记录 (Paint Records) | 描述如何绘制元素的指令。 |
合成 (Compositing) | 将多个图层组合成最终屏幕图像的过程。 |
图层 (Layers) | 可独立更新的页面部分,某些元素(如position: fixed )会创建独立图层。 |
瓦片 (Tiles) | 页面的小块区域,便于光栅化和缓存,提升渲染效率。 |
光栅化 (Rasterization) | 将矢量图形转为位图 |
重新布局 (Reflow) | 因DOM或CSS更改触发的布局重新计算,可能影响性能。 |
重绘 (Repaint) | 当元素外观更改时,重新绘制部分页面。 |
GPU加速 (GPU Acceleration) | 使用图形处理单元加速渲染任务,提升复杂图形和动画性能。 |
Draw Quad | Draw Quad(绘制四边形)是合成线程(Compositor Thread)在将页面内容渲染到屏幕上时使用的一种核心概念。它本质上是一个绘制指令,表示将某个特定的图像区域(通常是光栅化后的位图或纹理)绘制到屏幕上的某个位置。Draw Quad 是合成阶段的一个关键中间产物,用于将多个图层(Layers)和瓦片(Tiles)组合成最终的屏幕图像 |
2. 测试工具
webpagetest:在资源URL左侧用橙色圆圈标记渲染阻塞资源
3. 工作原理和过程
3.1. 浏览器架构
3.1.1. 浏览器架构
- Browser Process: 控制应用程序的"chrome"部分,包括地址栏、书签、后退和前进按钮。还处理网络浏览器中不可见的部分,例如网络请求和文件访问
- Render Process: Controls anything inside of the tab where a website is displayed
3.1.2. 多进程的好处
每个选项卡的都开一个渲染进程,当一个进程没有响应时,不会影响其他选项卡内容的渲染
3.1.3. 节省更多内存-Chrome中的服务化
当 Chrome 在强大的硬件上运行时,它可能会将每个服务拆分为不同的进程以提供更高的稳定性,但如果它在资源受限的设备上,Chrome 会将服务整合到一个进程中以节省内存占用
3.1.4. Per-frame renderer processes -Site isolation
Site isolation为每个站点iframe运行单独的渲染器进程。同源策略是网络的核心安全模型,它确保一个站点在未经同意的情况下无法访问其他站点的数据
3.2. 渲染流程
渲染器进程负责选项卡内的所有工作,最终产物流程如下
3.2.1. 解析HTML(DOM tree)
- 主线程(Main Thread)开始解析并构建文档对象模型(DOM)->DOM Tree
- 预加载扫描器(Preload Scanner) :
-
主线程在解析HTML的同时,运行一个轻量级的预加载扫描器。
- 扫描器识别并提前加载子资源
-
优化加载优先级
- DOM树是后续渲染的基础,预加载扫描器通过并行加载资源减少等待时间
3.2.2. 样式计算(DOM tree+CSSOM->Composer tree)
CSSOM与DOM树共同决定了页面的视觉表现,计算样式是布局的基础
-
CSS解析
- 主线程读取CSS文件(包括外部CSS、内联样式和标签中的样式)。
- CSS解析器将CSS代码转换为样式表对象(CSSOM,CSS Object Model)
即使不添加样式,dom结构自己也有默认样式
- 计算样式(Computed Style)
- 主线程遍历DOM树,为每个节点匹配CSS规则,计算最终样式->composer tree
3.2.3. 布局(composer tree=>layout tree)
有了DOM树和计算样式,浏览器进入布局阶段,生成布局树并计算元素的位置和大小,详细了解这部分内容可以阅读这里,布局树定义了页面元素的最终位置和大小,是绘制的前提
- 布局树(Layout Tree)构建
-
布局树基于DOM树和CSSOM,但有所筛选:
-
排除display: none的元素(完全不可见)。
-
包括伪元素(如p::before { content: "Hi!"; })。
-
布局树节点对应于页面的视觉结构
-
- 盒模型计算
- 位置和大小计算
3.2.4绘制(layout tree->Paint Records)
布局完成后,浏览器生成绘制指令,准备将页面内容渲染到屏幕上,绘制阶段将布局树的视觉信息转化为像素级的渲染指令
- 生成绘制记录(Paint Records)
- 绘制顺序由z-index和堆叠上下文(Stacking Context)决定
-
性能影响:
- 绘制是CPU密集型任务,频繁更新(如动画)可能导致性能问题。 -浏览器的目标是保持60帧每秒(16.6ms/帧),若主线程被JavaScript阻塞,可能导致卡顿
####3.2.5. 合成
绘制完成后,主线程将任务移交给合成线程(Compositor Thread),完成最终图像的生成和显示
-
提交绘制记录
- 主线程将绘制记录提交给合成线程,释放自身以处理其他任务
-
图层(Layers):合成线程将页面分成多个图层
-
瓦片(Tiles):每个图层被进一步分成小块(瓦片),通常为256x256或512x512像素。
- 瓦片化便于缓存和并行处理
-
光栅化(Rasterization:合成线程将瓦片转换为位图(Bitmap),并存储到GPU内存中
- GPU加速的光栅化显著提升性能
-
生成Draw Quad:合成线程为每个瓦片创建 Draw Quad,指定其纹理和屏幕坐标
- 考虑图层间的叠放顺序和透明度
-
显示(Viz组件) :
- 合成线程将 Draw Quad 列表发送给浏览器进程的 Viz 组件,Viz 组件利用 GPU 将 Draw Quad 渲染为最终图像,显示在屏幕上。
- 为加快初次渲染,Viz可能先显示低分辨率图像,随后替换为高质量版本
4. 实践
4.1. 非快速滚动区域的优化
事件委托会将目标元素区域都标记为非快速滚动区域,这样合成器线程每次都要等待主线程工作后才会工作,就丧失了流畅的用户体验,可以通过在监听事件加passive:true
解决,这向浏览器提示您仍想在主线程中收听事件,但合成器也可以继续合成新帧
csharp
document.body.addEventListener('touchstart', event => {
if (event.target === area) {
event.preventDefault()
}
}, {passive: true});
4.2. 获取完整的事件触发
对于大多数 Web 应用程序,合并事件应该足以提供良好的用户体验。但是,如果您正在构建诸如绘图应用程序并根据 touchmove坐标放置路径之类的东西,则可能会丢失中间坐标以绘制平滑线。在这种情况下,可以使用getCoalescedEvents指针事件中的方法来获取有关这些合并事件的信息
csharp
window.addEventListener('pointermove', event => {
const events = event.getCoalescedEvents();
for (let event of events) {
const x = event.pageX;
const y = event.pageY;
// draw a line using x and y coordinates.
}
});