渲染卡顿现象与 FPS 面板初探

你正在开发一个 H5 页面,滑动列表时明显感觉到一顿一顿的。点个按钮,半天才有反应。动画呢?像幻灯片一样一跳一跳的。这其实就是典型的渲染卡顿。

遇到这种情况,我的第一反应不是去翻代码,而是先打开 FPS 面板看一眼。为什么?因为数据不会骗人。帧率掉了多少、波动有多大,一清二楚。我个人习惯在开发阶段就一直开着这个面板,就像开车时盯着仪表盘一样自然。

核心观点: 渲染卡顿不是玄学,它是可量化、可观测的。FPS(Frames Per Second,每秒帧数)就是你最直接的量化指标。

卡顿的三种典型表现

卡顿不是一种现象,它有三种面孔。我根据这些年排查过的项目,把它们归纳为以下三类:

卡顿现象 典型表现 FPS 特征 内存泄漏关联度
帧率掉帧 画面卡顿、滑动不平滑、列表滚动像"爬" FPS 持续低于 30,甚至掉到个位数 ⭐⭐⭐
页面响应迟钝 点击无反馈、滚动有延迟、输入卡字 FPS 波动大,峰值低,间歇性掉帧 ⭐⭐⭐⭐
动画抖动 CSS 动画不连贯、transition 跳跃、JS 动画一顿一顿 FPS 忽高忽低,帧间隔不均匀 ⭐⭐⭐

帧率掉帧——说白了就是画面刷新跟不上你的操作。60fps 是人眼感觉流畅的分界线,一旦低于 30fps,卡顿感就非常明显了。我记得有一次排查一个资讯 feeds 流页面,滑动时 FPS 直接掉到 8,那种体验简直就是"幻灯片翻页"。

页面响应迟钝——点击按钮后 500ms 才有反馈,滚动条像被粘住了一样。这种情况通常是主线程被长时间任务阻塞了。你想想看,如果 JS 一直在跑 GC(垃圾回收),哪有空去处理你的点击事件?

动画抖动——这个最容易被发现。CSS animation 本该丝滑的,结果像踩了刹车一样一顿一顿的。我在项目中遇到过好几次,最后发现是定时器里不断创建新对象,导致内存持续增长,触发了频繁的 Major GC。

小技巧: 如果页面"刚打开时还行,越用越卡",那基本上可以锁定内存泄漏了。这是最典型的信号。

FPS 面板:实时监控的利器

Chrome DevTools 里有一个容易被忽略的神器——FPS Meter(实时帧率面板)。它藏在 Rendering 标签页里。

打开方式:

  1. 按 F12 或 Ctrl+Shift+I 打开 DevTools
  2. 按 Ctrl+Shift+P 打开命令菜单
  3. 输入 Show Rendering 并回车
  4. 在 Rendering 面板中勾选 Frame Rendering Stats

这时页面左上角就会出现一个实时 FPS 显示框。它长这样:

FPS: 58  |  帧耗时: 17.2ms  |  GPU 帧: 60

三个核心指标你要看懂:

  • FPS:当前每秒帧数。稳定在 55~60 是健康的,低于 30 就是警报线。
  • 帧耗时:每一帧的渲染耗时。超过 16.67ms(60fps 对应的帧预算)就意味着掉帧了。
  • GPU 帧:GPU 实际处理的帧数。如果这个值持续低于 FPS,说明 GPU 成了瓶颈。

我个人习惯在监控时重点关注帧耗时这个指标。为什么?因为 FPS 是平均值,但帧耗时能看到每一帧的抖动情况。有时候 FPS 显示 50 多,但帧耗时在 10ms 到 40ms 之间来回跳,这种"假流畅"最容易迷惑人。

避坑指南: 我曾经遇到过一个案例,FPS 面板显示 58,但用户就是觉得卡。后来用 Performance 面板录制才发现,每隔 2 秒就有一个 200ms 的长任务。FPS 平均值看着还行,但每一帧的耗时极不均匀。所以记住:不要只看 FPS 数字,更要看帧耗时的稳定性。

如果想在代码里实时监控 FPS,也可以写一个简单的监控脚本:

// FPS 实时监控(控制台版)
let frameCount = 0;
let lastTime = performance.now();

function checkFPS(now) {
  frameCount++;
  if (now - lastTime > 1000) {
    const fps = frameCount;
    console.log(`[FPS] ${fps}`);
    frameCount = 0;
    lastTime = now;

    if (fps < 30) {
      console.warn('⚠️ 出现严重掉帧,建议排查内存');
    } else if (fps < 50) {
      console.log('⚠️ 轻微掉帧,注意观察');
    }
  }
  requestAnimationFrame(checkFPS);
}
requestAnimationFrame(checkFPS);

把这段代码粘贴到控制台,就能实时看到 FPS 变化。我在项目中经常用它做快速诊断,比打开 DevTools 面板更灵活。

从卡顿现象到内存泄漏

好,现在你能识别卡顿了,也能看 FPS 了。但怎么跟内存泄漏扯上关系?

其实逻辑链条很简单:内存泄漏 → 堆内存持续增长 → GC 频率升高 → GC 暂停时间长 → FPS 掉帧 → 卡顿。

你打开 FPS 面板后,如果发现以下几种模式,就要高度怀疑内存泄漏了:

  • 模式一:刚打开页面时 FPS 正常(55~60),操作几分钟后逐渐掉到 30 以下。
  • 模式二:FPS 呈现"锯齿状"波动——突然掉到 10,又弹回 55,再掉下去。这通常是 Major GC 在"搞鬼"。
  • 模式三:帧耗时持续增长,从 12ms 慢慢涨到 40ms 甚至更高,且不回落。

这三种模式,说白了就是内存泄漏的"心电图"。我记得有一次排查一个在线编辑器,用户反馈"用着用着就卡死了"。打开 FPS 面板一看,完美符合模式一——初始 58fps,10 分钟后降到 15fps。最后定位到是一个未被清理的 ResizeObserver 回调里不断创建 DOM 节点。

排查口诀: "刚开流畅久变卡,帧率锯齿往下滑。帧耗只升不回头,内存泄漏不用愁。"
—— 记住这四句,现场排查时能快速定位方向。

当然,卡顿的原因不止内存泄漏一种。还有可能是重排重绘过多、图片加载太慢、长任务阻塞主线程等等。但内存泄漏有一个非常独特的特征——随时间累积且不可逆。其他原因导致的卡顿通常是瞬时的或者可恢复的,但内存泄漏会随着页面运行时间越长,卡顿越严重。

我的建议: 遇到卡顿问题,第一步不是看代码,而是打开 FPS 面板观察 3~5 分钟。记录 FPS 的变化趋势、帧耗时的波动范围、是否存在周期性掉帧。这些数据就是你后续排查的"体检报告"。
渲染卡顿现象与 FPS 诊断 帧率掉帧 FPS 持续低于 30 页面响应迟钝 操作延迟 & 滚动卡顿 动画抖动 画面跳跃不连贯 FPS 面板检测 Chrome DevTools 内存泄漏排查 堆快照 / DOM 节点分析 ⚠️ 内存泄漏 是卡顿的常见根因

看到这张图,你应该能理解本章的核心逻辑了:三种卡顿现象是"症状",FPS 面板是"听诊器",而内存泄漏排查是"病因诊断"。我们这一章先把症状和听诊器用熟,后续章节再深入剖析如何定位内存泄漏。

最后留一个思考题——你现在就可以打开任何一个你觉得"有点卡"的页面,打开 FPS 面板观察 3 分钟。看看它的 FPS 曲线是什么形状?是平稳的直线,还是锯齿状的波浪?这个习惯一旦养成,你对"卡顿"的理解会上一个台阶。