H5 页面渲染 10 万条列表卡顿,如何用虚拟滚动优化到 60 fps
解读
面试官把“10 万条列表 + 60 fps”同时抛出来,核心想验证三件事:
- 你是否真在移动端测过大数据量渲染,知道瓶颈在“DOM 量级”而非“数据量级”;
- 能否把性能指标拆解成可量化的测试用例(首帧、滚动帧率、内存峰值、GPU 占用);
- 给出方案后,能否用性能测试闭环验证优化效果,并反哺开发做进一步调优。
国内主流场景是微信、钉钉、企业微信内嵌 WebView,机型碎片化严重,低端机(如骁龙 625 + 4 GB)必须作为基线,否则线上 SLA 直接翻车。
知识点
-
渲染管线瓶颈
- 主线程:Recalculate Style → Layout → Paint → Composite,10 万节点 Layout 阶段 O(n) 爆炸;
- 层爆炸:每个 list item 若触发硬件加速,合成层数量 > 300 时,GPU 纹理上传直接把 Mali-G72 打满;
- 内存:单个节点 0.8 kB,10 万节点 ≈ 80 MB JS Heap + 120 MB RenderLayer,低端机触发 lmk;
- 滚动阻塞:TouchMove 回调耗时 > 16 ms 即掉帧,需保证 RAF 预算内完成。
-
虚拟滚动核心指标
- 可视区容量 n:以 iPhone 12 为例,一行 60 px,视口 780 px,n ≈ 13 行,buffer 取 2×n;
- 快速滚动 300 px/ms 时,缓冲回收延迟 < 100 ms,否则出现白屏;
- 回收率 = 1 – (DOM 节点数 / 总数据量),目标 ≥ 98 %;
- 帧率连续采样 5 s,最低帧率 ≥ 55 fps,抖动(jank)次数 ≤ 2。
-
性能测试闭环
- 埋点:在 requestAnimationFrame 回调里打 timestamp,计算两帧间隔 > 16.67 ms 记一次 jank;
- 自动化:用 Chrome DevTools Protocol + adb 脚本,在小米 6 上重复冷启动 10 次,取 P95;
- 对比基线:未优化版本 10 万条滚动平均帧率 18 fps,虚拟滚动后需提升到 60 fps,内存峰值下降 70 %;
- 报告:输出 trace 图、Layer 边框图、内存时序图,并给出 GPU 利用率曲线,方便开发定位层爆炸。
答案
“我会把优化拆成三步:测试诊断、虚拟滚动落地、指标回归,确保低端机也能稳 60 fps。
第一步,先复现并量化卡顿。
- 用公司云真机池里的红米 Note 9(联发科 G85)作为最低机型,Chrome 92 内核;
- 通过 cdp Performance.enable 录制 trace,发现 Layout 阶段平均 28 ms/帧,Paint 9 ms,GPU 纹理上传 12 ms;
- 同时 dump JavaScript Heap,节点数 100 003,总内存 212 MB,触发 lmk 概率 40 %。
第二步,虚拟滚动方案。
- 计算可视区行高 60 px,视口高 780 px,缓冲行数 26,总 DOM 节点控制在 30 个以内;
- 用 transform: translateY 做绝对定位,避免改变 top 值触发 Layout;
- 对快速滚动做节流:TouchMove 里只更新 scrollTop,RAF 里再计算 startIndex,降低回调频率;
- 图片懒加载 + 离屏解码,把解码任务放到 WebWorker,减少主线程阻塞;
- 针对国内微信 WebView,关闭 will-change: transform,防止合成层爆炸,改用 contain: layout paint 提示浏览器做裁剪。
第三步,性能回归。
- 写一段 Puppeteer 脚本,模拟手指快速滚动 3000 px,采集 5 s 帧率,最低帧率 58 fps,jank 次数 1;
- 连续滚动 200 次后,JavaScript Heap 稳定在 38 MB,GPU 内存 56 MB,lmk 未触发;
- 把报告同步到飞书多维表格,P95 数据达标后,才允许合并 MR,并加入每日巡检,防止业务再次回退。”
拓展思考
-
如果业务强制需要“右侧滚动条真实比例”,如何在不渲染全部节点的前提下计算总高度?
答:用平均高度估算 + 局部校正,滚动到底部时动态校准,误差控制在 2 % 以内,同时写单测验证。 -
当列表里嵌套可变高度图片,高度异步加载完成后会撑开容器,导致 scrollTop 跳变,如何量化并解决?
答:在性能测试脚本里加入“高度抖动”事件埋点,记录跳变次数与跳变像素,开发侧用 ResizeObserver 回调重置 translateY,并在 16 ms 内完成,保证帧率不掉。 -
国内低端机还有“GPU 带宽瓶颈”场景,如何进一步压测?
答:用 systrace 抓取 Mali 系列 GPU 的 GPU Util%,若滚动时利用率 > 85 %,则提示层过多或纹理过大,可建议开发把 2x 图降级到 1x,并开启 gpu-memory 日志做 P95 统计。