浏览器拿到一段 HTML 之后,其实做了四件事:解析出 DOM 树、解析 CSS 得到 CSSOM、把两者合成渲染树、再布局绘制成像素。理解这条链路,大部分"页面为什么卡"的问题就有了答案。

三个阶段

  1. 解析:HTML 逐字节转成 DOM,遇到外链样式表会阻塞后续脚本执行
  2. 样式计算:每个节点匹配选择器,算出最终的计算样式
  3. 布局与绘制:确定几何位置(Layout),填充像素(Paint),最后合成(Composite)

关键渲染路径

首屏只关心视口内的内容。CSS 是渲染阻塞资源,JS 是解析阻塞资源,所以首屏样式尽量内联,脚本尽量加 defer。

HTML
<!-- 首屏关键样式内联,其余异步加载 -->
<style>.hero{height:340px}</style>
<link rel="preload" as="style" href="/assets/later.css" onload="this.rel='stylesheet'">
<script src="/app.js" defer></script>
经验值:内联的首屏关键 CSS 控制在 14KB 以内,正好是一个 TCP 往返能带走的量。

重排和重绘不是一回事

改 width、margin、font-size 会触发重排(Layout),代价最大;只改 color、background 是重绘;改 transform、opacity 可以跳过前两步直接交给合成线程。

CSS
/* 动画只用合成器能处理的属性 */
.card:hover{transform:translateY(-4px);opacity:.92;transition:all .3s}

批量读尺寸再批量写样式,避免强制同步布局:

JS
// 反例:读写交替,每行都触发一次 layout
items.forEach(el => { el.style.width = el.offsetWidth + 'px' });
// 正例:先读完,再统一写
const w = items.map(el => el.offsetWidth);
items.forEach((el, i) => { el.style.width = w[i] + 'px' });

排查顺序建议:先看 Network 里的阻塞资源,再看 Performance 面板的紫色 Layout 条,最后才考虑换框架。