浏览器渲染原理浅析
从 URL 到页面
用户在地址栏输入 URL 后,浏览器经历以下过程:
- DNS 解析:将域名解析为 IP 地址
- TCP 连接:三次握手建立连接(HTTPS 额外有 TLS 握手)
- 发送 HTTP 请求:请求 HTML 文档
- 服务器响应:返回 HTML
- 浏览器解析渲染:将 HTML/CSS/JS 转换为像素
浏览器架构
主要进程
- 浏览器进程:管理标签页、地址栏、书签等
- 渲染进程(Chrome 中每个标签页独立):负责 HTML、CSS、JS 的解析和渲染
- GPU 进程:处理 GPU 任务,加速渲染
- 网络进程:处理网络请求
- 插件进程:管理插件
渲染进程内部线程
- 主线程:解析 HTML/CSS、执行 JS、计算样式、布局、绘制
- 合成线程:将图层合成,处理滚动、动画
- 光栅化线程:将图层转换为位图
关键渲染路径
第一步:解析 HTML → DOM 树
浏览器从网络进程接收 HTML 数据,逐步解析:
HTML 字节 → 字符 → 令牌(Token)→ 节点(Node)→ DOM 树
<html>
<body>
<h1>Hello</h1>
<p>World</p>
</body>
</html>
解析为 DOM 树:
Document
└── html
└── body
├── h1
│ └── "Hello"
└── p
└── "World"
注意:遇到 <script> 标签会暂停解析,直到脚本下载并执行完毕。将脚本放在底部或使用 defer / async 可避免阻塞。
第二步:解析 CSS → CSSOM 树
CSS 字节 → 字符 → 令牌 → 节点 → CSSOM 树
body { font-size: 16px; }
h1 { color: blue; }
CSSOM 树与 DOM 树类似,但包含层叠样式信息。CSS 解析不会阻塞 HTML 解析,但会阻塞渲染(因为需要完整的 CSSOM 才能渲染)。
第三步:合并 → Render 树
DOM 树 + CSSOM 树 → Render 树
Render 树只包含可见元素(display: none 的元素不包含,但 visibility: hidden 的元素包含)。
Render 树
├── body { font-size: 16px; }
├── h1 { color: blue; font-size: 16px; }
│ └── text "Hello"
└── p { font-size: 16px; }
└── text "World"
第四步:布局(Layout)
计算每个可见元素在视口中的位置和大小。从根节点开始递归计算:
- 盒模型尺寸(width、height、padding、border、margin)
- 位置(根据定位方式、浮动、flex、grid 等计算)
第五步:绘制(Paint)
将每个元素绘制到多个图层上。绘制顺序:
- 背景颜色/背景图片
- 边框
- 子元素
- 轮廓
第六步:合成(Composite)
图层合成到屏幕上。合成是 GPU 加速的关键步骤,只有 transform 和 opacity 的变化可以在合成阶段完成,不触发布局和绘制。
重排与重绘
重排(Reflow / Layout)
当改变影响布局的属性时触发:
- 尺寸:width、height、padding、border
- 位置:top、left、margin、position
- 内容变化:font-size、text 变化
- DOM 增加/删除
代价最高,需要重新进行布局、绘制和合成。
重绘(Repaint)
当改变外观但不影响布局时触发:
- color、background-color、visibility
- outline、box-shadow(不影响布局时)
代价中等,跳过布局阶段。
仅合成(Composite-only)
最佳的动画性能:
- transform: translate
- transform: scale
- opacity
代价最低,仅合成线程处理。
性能优化建议
减少重排
// 批量修改样式(避免逐条修改)
// 不推荐
element.style.width = '100px';
element.style.height = '100px';
// 推荐:使用 class
element.classList.add('new-style');
// 离线操作
const fragment = document.createDocumentFragment();
// 在 fragment 上操作...
document.body.appendChild(fragment);
// 使用 transform 替代 top/left
// 不推荐
element.style.top = '100px';
// 推荐
element.style.transform = 'translateY(100px)';
避免强制同步布局
// 错误:先读后写再读,触发强制同步布局
element.style.width = '100px'; // 写
const w = element.offsetWidth; // 读 → 强制同步布局
element.style.height = w + 'px'; // 写
// 正确:读写分离
element.style.width = '100px'; // 写
element.style.height = '100px'; // 写
const w = element.offsetWidth; // 读
总结
理解浏览器渲染原理是写出高性能前端代码的基础。核心要点是:尽量减少重排和重绘,使用合成友好的属性(transform、opacity)做动画,合理使用 will-change,读写 DOM 操作时注意批量处理。