本节摘要:渲染性能的可感知指标是帧率与长任务,观测工具是 DevTools 的性能面板;两大高频病灶是布局抖动(同步读写样式互踢)与合成层滥用(处处提升反而处处变慢)。本节讲观测方法、两个病灶的机理与处方、大列表的虚拟化取舍,并用一个滚动掉帧问题的完整诊断过程演示方法论。
屏幕每秒刷新数十次,每一帧的渲染要在约十六毫秒内完成(六十赫兹的标准),这叫帧预算。预算超支的表现就是掉帧:滚动不跟手、动画一顿一顿。DevTools Performance 面板录制几秒交互,能看到每一帧的时间构成——脚本执行、样式计算、布局、绘制、合成,哪段把预算吃爆,病灶就在哪段。另有一个轻量观测位:控制台的帧率监视器(Rendering 面板里的 FPS meter),适合快速验证"改了之后有没有变好"。
两类信号要分清:连续的短帧超标通常是脚本或布局问题;偶发的长任务(几十上百毫秒的阻塞)多半是一次性的重量计算,处方是拆分或移出主线程。
机理一句话:读样式会强制浏览器立刻结算布局,写样式让它作废。循环里交替读写,浏览器就被迫反复重排,每次都是同步成本:
// 抖动写法:循环里读写交替,每次读取都强制结算 const items = document.querySelectorAll('.row'); items.forEach(el => { const h = el.offsetHeight; // 读:强制布局 el.style.height = (h * 1.1) + 'px'; // 写:作废布局 }); // 批量写法:先集中读完,再集中写 const heights = items.map(el => el.offsetHeight); // 读阶段 items.forEach((el, i) => { el.style.height = (heights[i] * 1.1) + 'px'; // 写阶段 });
性能面板里布局抖动的签名很醒目:Layout 块反复出现且带紫色强制标记。处方除了批量读写,还有一个更彻底的方向——别用 JS 驱动布局:能用 CSS 完成的间距、折叠、过渡,交给 CSS;需要 JS 计算的,算好再一次到位。
Chromium 把页面分成若干合成层,动画只改 transform 和 opacity 时可以只动合成、不碰布局与绘制——这是高性能动画的正道。但"提升为合成层"本身有代价:每个层占显存、层过多时管理成本反噬,GPU 进程内存随之上涨(5.1 节归因表里的那一格)。
/* 正道:动画只碰 transform 与 opacity,浏览器走合成快路 */ .card-enter { transition: transform 200ms ease, opacity 200ms ease; } .card-enter.start { transform: translateY(8px); opacity: 0; } /* 歧途:动画几何属性,每一帧都触发布局与绘制 */ .card-wrong { transition: height 200ms, top 200ms; }
经验法则:列表项悬浮效果、入场动画、面板平移,一律用 transform 表达;will-change 提示符按需点给真正参与动画的元素,不要全页撒——它就是"给我升层"的显式请求,滥用等于把显存预算烧在不动的东西上。
上万行的列表(日志查看器、消息历史)是帧率杀手中的杀手。虚拟列表只渲染视口内的行加少量缓冲,滚动时动态换内容,DOM 规模从"行数"降到"视口行数"。代价是实现的复杂度:滚动条高度估算、快速滚动的占位策略、不定高行的测量。主流前端生态都有现成的虚拟化组件库,自造轮子前先摸一遍。判断标准很实际:几百行用普通列表加内容分页,几千行以上才值得上虚拟化。
背景:一个日志查看器,滚动到几万行规模后明显掉帧,用户形容"像在泥里滚"。
操作:性能面板录制滚动过程。帧时间线显示布局块密集出现且带强制标记——签名直指布局抖动。定位代码:滚动回调里逐行读取行高并写样式(为了给新行对齐)。顺带发现每行还挂着入场动画的 will-change,从未撤销。
处方:行高改为缓存测量(行高同质,测一次存值);滚动对齐改用 CSS 的 transform 定位;will-change 在动画结束后移除。三项改完复测,帧率回到满帧,GPU 进程内存也回落了两百兆。
结果之外还有个插曲:最初团队怀疑是"数据太多",差点给列表上虚拟化。测量纠正了方向——数据在内存里不碍事,病灶在渲染模式上,虚拟化救不了布局抖动。
解读:这个案例再次验证面板签名比直觉可靠。变式:如果掉帧签名出现在 Script 段(长任务),处方换成任务拆分(把大计算切片)或移入 Web Worker——渲染进程里的 Worker 是纯 Web 能力,正好符合沙箱时代"重计算不放主线程"的分工。 还有个团队协作层面的建议值得带上:把"帧预算十六毫秒"写进前端团队的共享词汇。当每个人心里有这把尺子,写循环时会自觉问一句"这一趟会不会超支",写下 will-change 前会想想它换的是显存——性能意识分散在日常的每个小决定里,比集中在一个专项里更耐用。
补一个排查动线的细节:录制性能面板前,先做一次垃圾回收并等界面完全静止三秒,让基线干净;录制时长控制在五到十秒——太短抓不到问题,太长分析时无从下手。录完先看概览条里最长的那一帧,点进去看它的时间构成,再决定往哪个细节面板钻。这套"先概览后下钻"的动线,能把新人第一次性能面板的使用体验从"信息轰炸"变成"按图索骥"。
页面跑得丝滑了,最后一节回到全局:多窗口与后台的资源策略,以及如何用性能预算守住优化成果。