渲染时间点

渲染时间点
渲染时间点

1. 解析 HTML - Parse HTML

解析 HTML
解析 HTML

DOM 树
DOM 树

CSSOM 树
CSSOM 树

每一个样式表会形成一个 CSStyleSheet 节点

  • <style></style>
  • <link ...>
  • <div style="color: red">
  • 浏览器默认样式表

body、div 等为什么是 block?因为默认样式表中设置了 display: block

head 为什么看不见?因为默认样式表中设置了 display: none

除了默认样式,其他样式 JS 都能操作

行内样式直接使用 dom.style 操作

<style></style><link ...> 可以在控制台中使用 CSSOM 树 document.styleSheets 操作

将网页中所有 div 都加边框:document.styleSheets[0].addRule('div', 'border: 2px solid #f40 !important')

HTML 解析过程中遇到 CSS 代码怎么办?

为了提高解析效率,浏览器会启动一个预解析器率先下载和解析 CSS

预解析线程-CSS
预解析线程-CSS

如果渲染主线程解析到 link 位置,此时外部的 CSS 文件还没下载解析好,主线程不会等待

如果解析到 script 位置,会停止解析 HTML,转而等待 JS 文件下载好,并将全局代码解析执行完成后,才能继续解析 HTML,因为 JS 代码中可能会操作 DOM 树

预解析线程-JS
预解析线程-JS

2. 样式计算 - Recalculate Style

样式计算
样式计算

会计算出最终样式(也叫计算后的样式,Computed Style)

最终样式
最终样式

自己写的样式可能很少,但最终样式可能会很多

在这一过程中,很多预设值会变成绝对值,比如 red 会变成 rgb(255,0,0)

相对单位会变成绝对单位,比如 em 会变成 px

这一步完成后,会得到一棵带有样式的 DOM 树

这个阶段算不出来相对单位转换后的绝对数值,比如 width: 70% 在这个阶段算不出来

JS 如何获取最终样式?getComputedStyle()

3. 布局 - Layout

布局
布局

根据 DOM 树(带样式)算出每个元素的位置、尺寸等,形成 Layout 树(包含每个节点的几何信息)

位置是相对包含块的位置(不是父级的位置)

比如说三个嵌套的盒子,最外面的盒子定位了,那么最里面的盒子的位置是相对已定位的盒子(最外面的盒子)的,而不是相对它的父级

同理,width: 70% 也是相对包含块的宽度

DOM 树和 Layout 树并不是一一对应的

Layout 树-1
Layout 树-1

Layout 树-2
Layout 树-2

Layout 树-3
Layout 树-3

JS 中不能直接获取 Layout 树的对象,但是能间接获取一些信息:

document.body.clientWidth

4. 分层 - Layer

分层
分层

与堆叠上下文有关的属性(如 z-index)可能会影响分层决策

可以把一些经常变化的元素分为一层,将来变动时只会变动这层的内容

可以使用 will-change 告诉浏览器「这个元素稍后可能会发生某些变化,请提前做好准备!」

will-change 是图层管理的开发接口,允许开发者精细化控制渲染性能,直接影响浏览器渲染管线的优化策略

.box {
  will-change: transform;
  transition: transform 0.3s;
}

.box:hover {
  transform: scale(1.2);
}
  1. will-change

    元素在首次 transform 变化时,浏览器才创建图层 → 可能导致首帧卡顿

  2. will-change

    浏览器在样式计算阶段就分配独立图层 → 动画开始时直接走 GPU 合成

5. 绘制 - Paint

这里的绘制是指生成绘制的指令

绘制
绘制

类似于 Canvas

渲染主线程的工作到此为止,剩余步骤交给其他线程完成

渲染主线程完毕
渲染主线程完毕

6. 分块 - Tiling

分块会将每一层分为多个小的区域

分块
分块

会先画靠近视口的块

合成线程-分块
合成线程-分块

合成线程会从线程池中拿取多个线程来完成分块工作

7. 光栅化 - Raster

光栅化是将每个块变成位图(也就是变为每个像素点的信息)

优先处理靠近视口的块

GPU 进程-光栅化
GPU 进程-光栅化

光栅化是在 GPU 中做的,所以要用到 GPU 进程

8. 画 - Draw

画

合成线程计算出每个位图在屏幕上的位置,生成 quad(指引信息),交给 GPU 进行最终呈现

为什么要用 GPU 进程中转一下?明明可以直接通知 GPU 的

因为渲染主线程和合成线程处于渲染进程中,渲染进程是在沙盒中的,隔离了硬件,因此必须通过 GPU 进程中转才能通知到 GPU

指引会标识出每个位图应该画到屏幕的哪个位置,以及会考虑到旋转、缩放等变形

为什么说 transform 效率高?因为 transformopacity 等发生在合成线程,与渲染主线程无关

transform 不会改变元素的布局信息,也就是说,不会触发回流(Reflow),它只是在渲染的最终阶段对元素的视觉输出进行变形

transform: translateX(50px),视觉上,浏览器在合成阶段将该元素整体平移 50px,但布局系统不知道这个变化

只有 transformopacity 能完全避免回流(Reflow)

完整过程

渲染完整过程
渲染完整过程