浏览器渲染原理
浏览器架构
多进程架构
主要进程:
- 浏览器主进程:界面显示、用户交互、子进程管理
- 渲染进程:页面渲染、JS 执行
- GPU 进程:3D 绘制、CSS 动画
- 网络进程:网络资源加载
- 插件进程:浏览器插件
渲染进程组成
线程:
- 主线程:JS 执行、DOM 解析、样式计算、布局、绘制
- 合成线程:图层合成
- 光栅化线程:图层分块、光栅化
- Worker 线程:Web Worker
渲染流程
1. 构建 DOM 树
HTML 解析:
HTML 字节流 → 字符流 → Token → 节点 → DOM 树
解析过程:
- 字节流转换为字符
- 词法分析生成 Token
- 语法分析构建节点
- 构建 DOM 树
阻塞情况:
- JS 会阻塞 DOM 解析
- CSS 不会阻塞 DOM 解析,但会阻塞渲染
2. 构建 CSSOM 树
CSS 解析:
CSS 字节流 → Token → 节点 → CSSOM 树
特点:
- 从右到左匹配选择器
- 解析会阻塞渲染
3. 构建渲染树(Render Tree)
过程:
- 遍历 DOM 树
- 找到对应的 CSS 规则
- 创建渲染对象(RenderObject)
- 构建渲染树
不包括:
display: none的元素<head>中的元素<script>、<meta>等
4. 布局(Layout/Reflow)
计算位置和大小:
- 计算每个元素的几何信息
- 确定元素在页面中的位置
触发条件:
- 添加或删除 DOM 元素
- 改变元素位置、尺寸
- 改变窗口大小
- 改变字体大小
5. 绘制(Paint)
绘制过程:
- 遍历渲染树
- 调用渲染对象的
paint方法 - 绘制到图层
绘制顺序:
- 背景色
- 背景图片
- 边框
- 子元素
- 轮廓
6. 合成(Composite)
图层合成:
- 将多个图层合并
- 输出到屏幕
合成层:
transform、opacity会创建合成层- 合成层由 GPU 处理,性能更好
关键渲染路径优化
阻塞渲染的资源
CSS:
- 阻塞渲染
- 应该放在
<head>中 - 关键 CSS 可以内联
JavaScript:
- 阻塞 DOM 解析
- 应该放在
</body>前 - 或使用
async、defer
<!-- 异步加载,不阻塞解析 -->
<script async src="script.js"></script>
<!-- 延迟执行,不阻塞解析 -->
<script defer src="script.js"></script>
优化策略
1. 减少关键资源数量
- 内联关键 CSS
- 延迟非关键 CSS
- 异步加载非关键 JS
2. 减少关键资源大小
- 压缩 CSS/JS
- 移除未使用的 CSS
- Tree Shaking
3. 缩短关键渲染路径长度
- 减少 CSS 层级
- 优化选择器
- 减少 DOM 层级
重排和重绘
重排(Reflow)
触发条件:
- 改变窗口大小
- 改变字体大小
- 添加/删除 DOM 元素
- 改变元素位置、尺寸
- 读取布局属性(offsetTop、scrollTop 等)
优化:
// ❌ 不好:多次重排
element.style.width = '100px';
element.style.height = '100px';
element.style.margin = '10px';
// ✅ 好:一次重排
element.style.cssText = 'width: 100px; height: 100px; margin: 10px;';
// ✅ 更好:使用 class
element.className = 'new-style';
重绘(Repaint)
触发条件:
- 改变颜色
- 改变背景
- 改变边框样式
- 改变 visibility
优化:
- 使用
transform和opacity(触发合成层) - 批量修改样式
- 使用
will-change提示浏览器
合成(Composite)
合成层:
- 使用
transform、opacity、will-change - 由 GPU 处理,性能最好
- 不会触发重排和重绘
/* ✅ 创建合成层,GPU 加速 */
.element {
transform: translateX(100px);
opacity: 0.5;
will-change: transform;
}
渲染优化
1. 减少重排重绘
方法:
- 使用
transform和opacity - 批量 DOM 操作
- 使用 DocumentFragment
- 避免频繁读取布局属性
2. 使用合成层
创建合成层:
transform: translateZ(0)opacity < 1will-changeposition: fixed
注意:
- 合成层过多会消耗内存
- 合理使用,不要过度
3. 优化选择器
选择器性能:
/* ✅ 快:ID 选择器 */
#header { }
/* ✅ 快:类选择器 */
.header { }
/* ⚠️ 慢:属性选择器 */
[data-id="header"] { }
/* ❌ 很慢:伪类选择器 */
:not(.header) { }
4. 避免强制同步布局
问题代码:
// ❌ 强制同步布局
for (let i = 0; i < items.length; i++) {
items[i].style.width = items[i].offsetWidth + 10 + 'px';
}
优化:
// ✅ 先读取,再写入
let widths = [];
for (let i = 0; i < items.length; i++) {
widths[i] = items[i].offsetWidth;
}
for (let i = 0; i < items.length; i++) {
items[i].style.width = widths[i] + 10 + 'px';
}
浏览器缓存
缓存位置
1. Service Worker
- 可编程缓存
- 离线可用
2. Memory Cache
- 内存缓存
- 关闭标签页清除
3. Disk Cache
- 磁盘缓存
- 持久化存储
4. Push Cache
- HTTP/2 推送缓存
- 会话级别
缓存流程
1. 检查 Service Worker
2. 检查 Memory Cache
3. 检查 Disk Cache
4. 发起网络请求
5. 存入缓存
性能指标
关键时间点
Navigation Timing:
navigationStart:开始导航domLoading:开始解析 DOMdomInteractive:DOM 解析完成domContentLoaded:DOMContentLoaded 事件domComplete:DOM 和资源加载完成loadEventEnd:load 事件结束
计算指标:
const timing = performance.timing;
// DNS 查询时间
const dnsTime = timing.domainLookupEnd - timing.domainLookupStart;
// TCP 连接时间
const tcpTime = timing.connectEnd - timing.connectStart;
// 请求响应时间
const requestTime = timing.responseEnd - timing.requestStart;
// DOM 解析时间
const domParseTime = timing.domInteractive - timing.responseEnd;
// 页面加载时间
const loadTime = timing.loadEventEnd - timing.navigationStart;
最佳实践
1. 优化关键渲染路径
- 内联关键 CSS
- 异步加载非关键资源
- 减少 DOM 层级
- 优化选择器
2. 减少重排重绘
- 使用
transform和opacity - 批量 DOM 操作
- 避免强制同步布局
3. 合理使用合成层
- 动画使用合成层
- 避免过度使用
- 注意内存占用
4. 优化资源加载
- 使用 CDN
- 启用压缩
- 使用 HTTP/2
- 预加载关键资源