1. 卡顿的本质:从帧率到垂直同步
做前端性能优化这么多年,我始终觉得——理解卡顿,比学会优化技巧更重要。你连敌人长什么样都不知道,怎么打?
今天咱们就从最底层开始,把「卡顿」这层窗户纸捅破。
1.1 帧率(FPS)—— 流畅度的硬指标
FPS,全称 Frames Per Second,每秒传输帧数。说白了就是——你的屏幕每秒钟能刷新多少张画面。
我习惯把帧率想象成翻页动画书:
- 10 FPS:像幻灯片,一卡一卡的
- 30 FPS:勉强能看,但明显不流畅
- 60 FPS:丝滑,人眼基本感觉不到卡顿
- 120 FPS:极致流畅,但普通显示器看不出区别
60 FPS 为什么是黄金标准?
因为人眼的视觉暂留效应大约在 16.67ms。60 FPS 意味着每帧耗时 16.67ms,刚好卡在这个阈值上。再慢,人眼就能感知到「跳帧」了。
我在项目中遇到过不少同学,一上来就盯着 FPS 数字看。其实 FPS 只是结果,不是原因。真正要搞懂的,是 为什么 FPS 会掉。
1.2 掉帧的底层原理 —— 渲染流水线
一个页面从代码到屏幕,要经过这么几步:
- JavaScript 执行 —— 处理事件、执行动画逻辑
- 样式计算 —— 计算 CSS 规则匹配
- 布局 —— 计算元素位置和尺寸
- 绘制 —— 生成绘制指令
- 合成 —— 将图层合并成最终画面
- 提交到 GPU —— 显示到屏幕
这六步必须在 16.67ms 内完成。任何一步超时,都会导致这一帧「掉队」。
注意: 掉帧 ≠ 卡顿。偶尔掉一帧人眼察觉不到,连续掉帧才会产生卡顿感。我见过很多优化方案,把精力花在「消灭所有掉帧」上,其实没必要。重点应该是 减少连续掉帧。
1.3 垂直同步(VSync)—— 屏幕的节拍器
你想想看,如果没有 VSync,会发生什么?
GPU 渲染完一帧就立刻推送到屏幕。但屏幕刷新是有固定周期的(比如 60Hz 显示器每 16.67ms 刷新一次)。如果 GPU 在屏幕刷新到一半时推送新画面,就会出现 画面撕裂——上半部分是旧帧,下半部分是新帧。
VSync 的作用就是:强制 GPU 等待屏幕刷新完成后再推送下一帧。
| 模式 | 工作原理 | 优点 | 缺点 |
|---|---|---|---|
| 关闭 VSync | GPU 渲染完立即推送 | 帧率不受限,操作延迟低 | 画面撕裂 |
| 开启 VSync | GPU 等待屏幕刷新完成 | 画面完整,无撕裂 | 帧率被锁定在屏幕刷新率,可能引入输入延迟 |
| 自适应 VSync | 帧率高于刷新率时开启,低于时关闭 | 兼顾流畅和完整 | 实现复杂,部分场景仍有撕裂 |
嗯,这里要注意——VSync 不是万能的。我曾经在一个项目中,发现页面偶尔卡顿,排查了半天,最后发现是 VSync 导致的帧率抖动。因为 GPU 渲染时间刚好卡在 16.67ms 边缘,VSync 让它等了一帧,反而造成了掉帧。
1.4 从帧率到卡顿的完整链路
为了让你更直观地理解,我画了一张图:
1.5 实战中如何定位卡顿
光懂理论不行,得能落地。我分享几个我在项目中常用的方法:
我的排查三板斧:
- Chrome DevTools Performance 面板 —— 录制一段操作,看每一帧的耗时分布。红色标记就是掉帧。
- Frame Rendering Stats —— 在 Chrome 里打开
chrome://gpu,查看 GPU 渲染状态。 - 手动打点 —— 用
performance.now()在关键步骤前后打点,计算耗时。
举个例子,我曾经接手过一个 H5 活动页,滑动时卡得不行。打开 Performance 面板一看,好家伙——布局(Layout)耗时 40ms,远超 16.67ms 的预算。
原因是什么?页面里用了大量 position: absolute 的动画,每次滚动都触发了全量重排。后来改成 transform 实现,布局耗时直接降到 2ms。
避坑指南: 我曾经以为 FPS 越高越好,于是在一个项目中强行把帧率推到 120 FPS。结果呢?手机发烫、电池狂掉,用户反而投诉了。后来我才明白——稳定 60 FPS 比偶尔 120 FPS 更重要。性能优化的目标不是跑分,是用户体验。
1.6 小结
这一章我们搞清楚了:
- 卡顿的本质 —— 渲染流水线中某一步超时,导致帧被跳过
- 60 FPS 的黄金标准 —— 每帧 16.67ms 的硬约束
- VSync 的作用 —— 协调 GPU 和屏幕的节奏,避免撕裂
- 掉帧 ≠ 卡顿 —— 连续掉帧才是真凶
嗯,基础打牢了,后面咱们才能聊具体的优化手段。记住一句话:先理解,再优化。