先说问题:380ms的长任务从哪来
2024年3月,商城活动页收到客诉:iPhone 12上白屏接近3秒,下滑商品流掉帧严重。录屏逐帧数,从点击链接到首张商品图出现在屏幕上,2.8秒。滚动时画面每秒跳4~5次。
第一轮优化执行的是常规动作:压缩图片、拆JS bundle、资源加CDN。FCP从2.8s降到1.9s,但滚动依然卡。用Chrome 119 DevTools Performance录制10秒滚动,主线程全是紫色长任务块,最长380ms。点开Bottom-Up面板,三个热点函数:getBoundingClientRect()、React 18.2.0的setState、LayerTree更新。全部集中在scroll事件里。
这个现象叫「强制同步布局」(Forced Synchronous Layout)。但强制同步布局只是表象。真正的问题是:整个团队对浏览器从HTML到像素的完整路径缺乏统一认知,所有优化都在“资源体积”上做文章,没人回答“屏幕上的像素是怎么画出来的”。
渲染流水线:六个环节,一个都不能错
浏览器把HTML变成屏幕上的像素,走六个步骤:
| 阶段 | 输入 | 输出 | 执行线程 |
|---|---|---|---|
| 1. DOM构建 | HTML字节流 | DOM Tree | 主线程 |
| 2. CSSOM构建 | CSS字节流 | CSSOM Tree | 主线程 |
| 3. RenderTree合并 | DOM + CSSOM | 可见节点渲染树 | 主线程 |
| 4. Layout布局 | RenderTree | 盒模型几何坐标 | 主线程 |
| 5. Paint绘制 | 布局结果 | 位图图层 | 主线程 + 栅格线程 |
| 6. Composite合成 | 多个位图图层 | 屏幕最终画面 | GPU合成线程 |
DOM构建:HTML不是一次解析完的
HTML解析器从网络拿到的是一块一块的数据。解析器做「投机性解析」:遇到、