文章摘要
从解析 HTML 到最终绘制像素,梳理关键渲染路径上的每一步,以及可以动手优化的地方。
浏览器拿到一段 HTML 之后,其实做了四件事:解析出 DOM 树、解析 CSS 得到 CSSOM、把两者合成渲染树、再布局绘制成像素。理解这条链路,大部分"页面为什么卡"的问题就有了答案。
三个阶段
- 解析:HTML 逐字节转成 DOM,遇到外链样式表会阻塞后续脚本执行
- 样式计算:每个节点匹配选择器,算出最终的计算样式
- 布局与绘制:确定几何位置(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 条,最后才考虑换框架。
留言板0
昵称必填 · 邮箱仅用于生成头像
还没有留言,来坐第一排
1231231😭
第一条留言,测试<script>alert(1)</script>