1、掉帧的本质:什么是掉帧、帧率与刷新率的关系、掉帧的视觉表现

做动画开发这些年,我见过最多的 Bug 就是掉帧。说实话,掉帧这个问题,说难也难,说简单也简单。难的是你找不到根因,简单的是——一旦你理解了它的本质,修复思路就清晰了。

这一章,我们先不急着写代码。先把掉帧的「底裤」扒干净。

1.1 什么是掉帧?

掉帧,字面意思就是「丢掉了本该渲染的那一帧」。你想想看,动画本质是一连串静止画面快速切换,骗过眼睛,让人感觉物体在动。如果中间少了几帧,画面就会「卡一下」。

我习惯用一个比喻:动画就像翻书小人书。你快速翻页,小人就在跑。如果你翻得忽快忽慢,甚至跳过了几页,小人就会「瞬移」——这就是掉帧的直观感受。

掉帧的本质:渲染管线未能在规定的帧时间窗口内完成一帧的绘制与提交,导致该帧被丢弃或延迟显示。

说白了,就是你的代码跑得太慢了,或者 CPU/GPU 忙不过来,没赶上下一帧的「发车时间」。

1.2 帧率与刷新率的关系

这两个概念经常被混为一谈,但它们是两码事。

概念 定义 单位 谁决定的
帧率(FPS) 应用每秒实际渲染的画面数量 fps(frames per second) 应用代码 + 设备性能
刷新率(Hz) 显示器每秒刷新屏幕的次数 Hz(Hertz) 硬件(屏幕面板)

举个例子:你的显示器是 60Hz,意味着它每 16.67ms 刷新一次。如果你的动画能稳定跑 60fps,那每一帧刚好卡在刷新点上,画面丝滑。

但如果你只跑了 30fps,会发生什么?显示器依然每 16.67ms 刷新一次,但你的应用只提供了 30 张画面。于是显示器不得不把同一张画面显示两次——这就是「重复帧」。重复帧虽然不叫掉帧,但视觉上已经能感觉到卡顿了。

我的经验:我在项目中遇到过很多次,开发者只看 FPS 数字觉得「有 50fps 还不错啊」,但用户反馈卡。为什么?因为 50fps 在 60Hz 屏幕上,帧间隔不均匀,有的帧只用了 10ms,有的用了 30ms,视觉上就是一顿一顿的。所以别只看平均 FPS,要看帧间隔的稳定性。

1.3 掉帧的视觉表现

掉帧不是只有一种样子。我把它分成三种常见类型:

  • 微卡顿(Micro-stutter):画面偶尔「抖」一下,持续时间很短,但能感知到。通常是因为某帧渲染超时了 2-3ms。
  • 明显跳帧(Visible skip):物体位置突然跳跃,中间过程被吞掉了。比如一个球从 A 点滚到 B 点,中间少了 3 帧,球就「瞬移」了。
  • 冻结(Freeze):画面卡住不动,持续几百毫秒甚至更久。这通常是主线程被长时间阻塞,比如 JS 执行了耗时任务。

为什么会这样?我画了一张图,帮你理解掉帧的「视觉成因」:

掉帧视觉表现成因图 正常 60fps → 每帧间隔 ≈ 16.67ms,均匀 微卡顿 → 某帧耗时 28ms,间隔不均 明显跳帧 → 连续 2 帧超时,画面跳跃 冻结 → 主线程阻塞,多帧被丢弃 掉帧越严重,视觉上「卡」的感觉越明显,用户流失率越高

注意:我曾经接手过一个项目,产品经理说「偶尔卡一下,不影响使用」。结果用户反馈里「卡顿」是最高频投诉词。掉帧不是「偶尔的小问题」,它是用户体验的杀手。尤其是电商、游戏、视频类应用,掉帧直接等于流失用户。

1.4 掉帧的「罪魁祸首」

掉帧的原因千奇百怪,但归根结底,逃不出这几个大类:

  1. 主线程阻塞:JS 执行时间过长,比如大循环、DOM 操作、JSON.parse 大对象。渲染线程只能等着。
  2. 布局抖动(Layout Thrashing):反复读写 DOM 几何属性,强制触发回流。我见过最夸张的案例,一帧内触发了 200 多次回流。
  3. 重绘开销过大:比如用了大量阴影、渐变、滤镜,GPU 扛不住了。
  4. 内存问题:频繁 GC(垃圾回收)导致卡顿,或者内存泄漏导致页面越来越慢。
  5. 资源加载:图片、字体、视频等资源加载时阻塞了渲染。

嗯,这里要注意:很多时候掉帧不是单一原因造成的,而是多个因素叠加。比如你既有布局抖动,又碰上了 GC,那帧时间直接爆炸。

1.5 怎么「看见」掉帧?

光靠肉眼不行。你得用工具。

  • Chrome DevTools Performance 面板:录一段性能数据,看帧时间线。红色标记就是掉帧。
  • FPS Meter:实时显示帧率,适合快速验证。
  • Frame Rendering Stats:Chrome 的 GPU 渲染统计,能看到每一帧的绘制耗时。

我个人习惯先用 FPS Meter 快速扫一遍,发现问题了再用 Performance 面板深挖。别一上来就录 Performance,数据太多反而找不到重点。

一个小技巧:在 Performance 面板里,重点关注「Frame」那一行。如果看到红色的长条,点进去看「Summary」,它会告诉你这一帧里 JS、样式、布局、绘制各花了多少时间。哪个环节超时了,一目了然。