1. 卡顿的本质:从用户感知到系统原理

做性能优化这么多年,我见过太多团队一上来就怼代码、改逻辑,结果卡顿依旧。为什么?因为他们没搞明白一个最根本的问题——卡顿到底是什么。

说白了,卡顿就是用户眼睛看到的画面「不连贯」。但「不连贯」这三个字背后,藏着一整套从硬件到软件的流水线。今天我们就从这条流水线的最源头开始拆。

1.1 用户感知的「流畅」到底有多苛刻?

人眼对画面变化的感知能力,其实比我们想象中要敏感得多。科学研究表明,当画面刷新率低于 10-12 fps 时,人眼就能明显感觉到「一帧一帧」的跳跃感。但真正让用户觉得「流畅」的门槛,远不止这个数。

关键数据:

  • 10-12 fps:能感知到明显卡顿
  • 24 fps:电影级,勉强可接受
  • 30 fps:基础流畅,但快速移动时仍有模糊
  • 60 fps:业界公认的「流畅」黄金标准
  • 90/120 fps:电竞级,人眼几乎无法分辨单帧

你想想看,为什么电影 24 帧就够了,而 UI 界面非要 60 帧?因为电影画面有运动模糊,每一帧之间大脑会自动补全。但 UI 界面是静态元素在移动,没有模糊,每一帧都必须清晰锐利。一旦掉帧,大脑立刻就能察觉。

我个人习惯把 60 fps 当作一条红线。低于它,用户就会开始皱眉。

1.2 60fps 与 16ms 的黄金法则

60 fps 意味着什么?我们来算一笔账:

1 秒 = 1000 毫秒
1000 ms ÷ 60 帧 ≈ 16.67 ms/帧

也就是说,每一帧只有 16.67 毫秒的时间来完成所有工作。从用户点击屏幕,到系统响应,再到 GPU 把画面绘制出来,最后显示到屏幕上——这一切,必须在 16ms 内搞定。

超过 16ms,下一帧就会延迟。延迟累积,用户就看到了卡顿。

我的经验: 很多开发者觉得 16ms 很宽裕。但实际项目中,光是一层布局计算就可能吃掉 8-10ms,再加上渲染、合成、光栅化……16ms 其实非常紧张。我曾经在一个电商首页的优化中,发现一个列表项的 layout 耗时 12ms,直接导致整个页面掉到 40fps。

1.3 一帧画面到底经历了什么?

要理解卡顿,就得先理解一帧是怎么产生的。我把它拆成四个阶段:

  1. 输入处理:触摸、滚动、点击等事件被系统捕获
  2. 动画/逻辑计算:JS 执行、样式计算、布局(Layout)
  3. 绘制与合成:Paint、Composite、光栅化
  4. 显示输出:GPU 将帧缓冲区内容送到屏幕

这四个阶段,任何一个环节超时,都会导致这一帧「流产」。而最容易被忽略的,其实是第 2 阶段的 Layout(布局) 和第 3 阶段的 Paint(绘制)。

避坑指南: 我曾经接手过一个项目,页面滚动时疯狂掉帧。排查后发现,罪魁祸首是某个组件在滚动事件里反复修改了元素的 width 和 height,触发了强制同步布局(Forced Synchronous Layout)。每次滚动都要重新计算整个页面的布局,16ms 根本不够用。

1.4 卡顿的两种类型:视觉卡顿 vs 交互卡顿

在实际项目中,我习惯把卡顿分成两类:

类型 表现 根因 典型场景
视觉卡顿 画面跳动、撕裂、掉帧 渲染流水线超时 列表滚动、动画播放
交互卡顿 点击无反应、延迟响应 主线程被阻塞 按钮点击、输入框打字

视觉卡顿通常跟 GPU 和渲染管线有关,而交互卡顿往往是因为 JS 执行时间过长,占用了主线程。两者有时会同时出现,但排查思路完全不同。

1.5 核心知识体系:卡顿分析的四个维度

为了让你对整个知识体系有个全局视角,我画了一张图。这张图也是后续所有章节的骨架:

卡顿分析四维框架 卡顿本质 维度一:渲染管线 Layout → Paint → Composite 维度二:主线程 JS执行 · 事件处理 维度三:GPU/合成 光栅化 · 纹理上传 维度四:内存/IO GC · 磁盘读写 强制同步布局 长任务阻塞 图层过多 内存抖动 核心原则 • 每一帧必须在 16ms 内完成所有阶段 • 任何一个维度超时,都会导致用户感知到卡顿 • 优化要从「最慢的环节」入手,而不是盲目改代码

这张图涵盖了后续 29 章的核心脉络。你可以把它当作一张地图——每次遇到卡顿,先定位是哪个维度出了问题,再深入排查。

1.6 为什么 16ms 这么容易超时?

嗯,这里要注意。16ms 看起来不少,但实际分配下来是这样的:

  • 输入处理:约 2-3ms
  • JS 执行 + 样式计算:约 4-6ms
  • 布局(Layout):约 3-5ms
  • 绘制(Paint):约 2-4ms
  • 合成 + GPU 上传:约 2-3ms

加起来已经接近 16ms 了。这还是在理想情况下。一旦出现强制同步布局、长任务、或者图层爆炸,任何一个环节翻倍,整帧就崩了。

一个真实案例: 我曾经优化过一个资讯类 App 的首页。用户反馈「滑动时一卡一卡的」。我用 Chrome DevTools 的 Performance 面板录制了一段滚动,发现 Layout 阶段耗时高达 18ms。进一步排查,发现是因为某个广告位在滚动时不断修改 top 值,触发了全量布局。解决方案很简单:把 top 改成 transform: translateY(),Layout 耗时从 18ms 降到了 0.3ms。页面瞬间回到 60fps。

1.7 本章小结

这一章我们只做了一件事:重新定义卡顿。它不是玄学,不是感觉,而是 16ms 内完不成一帧渲染的客观事实。后续所有章节,都会围绕这个核心展开。

记住三个数字:60fps、16ms、四个维度。这是你作为性能优化工程师的底层直觉。