从输入 URL 到像素的旅程

在用户眼里,输入网址到看到页面是一瞬间的事;在浏览器内部,这趟旅程要经过好几个站:请求 HTML、解析成 DOM 树、解析 CSS 成样式树、两棵树合并成渲染树、计算每个元素的位置(布局)、画成像素(绘制)、最后合成到屏幕。理解这条流水线,是所有首屏优化的前提——不知道卡在哪一站,优化就是瞎猜。

渲染优化的本质,是让关键内容尽可能早地走完这条流水线,而不是让 JS 和 CSS 把路堵住。
本文核心知识点
  • 渲染管线解析 → 布局 → 绘制 → 合成,四步各有成本。
  • 重排几何变化触发重新布局,成本最高。
  • 重绘外观变化只重画,不重排,成本较低。
  • 关键 CSS首屏样式内联,避免等外部 CSS 才渲染。
浏览器渲染原理配图:蓝色渐变背景上浏览器窗口经中央发光内容块,光线自上而下流过流水线,右侧像素屏幕逐块点亮
光线自上而下流过解析、布局、绘制各站,首屏内容块最先点亮——这就是渲染流水线。

关键渲染路径

浏览器拿到 HTML 后不会立刻渲染:遇到 CSS 要先等它加载解析,因为没有样式就不知道元素长什么样;遇到没有 defer 的 JS 也要停下来,因为 JS 可能会改 DOM 和样式。这意味着首屏快慢,很大程度上取决于 HTML、CSS、JS 三个资源的加载与解析顺序。

关键渲染路径优化的目标,就是缩短"从开始加载到首次渲染"的距离:让 CSS 尽快可用、让非关键 JS 不要阻塞、让首屏内容在 HTML 里靠前。

重排与重绘:什么时候变慢

页面加载完成后,交互时的卡顿多来自重排(Reflow)与重绘(Repaint)。修改元素的宽度、高度、位置、字体大小,会触发浏览器重新计算布局(重排),连带影响周围元素;只改颜色、背景,不影响布局的,只触发重绘。重排比重绘贵得多,因为它要重新走一遍布局计算。

// 触发重排:改宽度,整个布局都要重算
el.style.width = '300px';

// 只触发重绘:改颜色,不动布局
el.style.color = 'blue';

// 性能写法:先离线改完,再一次性放回去
el.style.display = 'none';
update(el);                 // 期间的多次改动只引发一次重排
el.style.display = 'block';

频繁读取 offsetTop 再立即改样式,会强制浏览器同步布局(Layout Thrashing),是动画卡顿的常见原因。

JS 阻塞了渲染的什么

JS 之所以阻塞渲染,是因为它可能改动 DOM 结构和样式。浏览器解析到一个不带 defer/async 的脚本时,会停下来先下载执行它,再继续解析后面的 HTML。所以把巨大的 JS 放在 head 里,首屏就要先等它执行完。

解决思路:非关键脚本加 defer(等 HTML 解析完再执行,不阻塞渲染)、关键路径之外的代码异步加载、首屏用服务端渲染或静态 HTML 直出,让用户先看到内容,JS 再在后台接管交互。

首屏优化:关键 CSS 与骨架屏

两个最有效的首屏手段。关键 CSS 内联:把首屏可见区域用到的样式直接写在 HTML 的 style 标签里,浏览器不用等外部 CSS 文件就能先把首屏画出来,其余样式异步加载。骨架屏:在真实内容到达前,先显示和布局一致的灰色占位块,让用户感知"页面正在加载"而不是一片白。

<!-- 关键 CSS 直接内联在 head,不等外部文件 -->
<style>
  .hero{height:40vh;background:linear-gradient(135deg,#1e5fa8,#3aa0e8)}
  .nav{display:flex;align-items:center;gap:16px}
</style>
<link rel="stylesheet" href="/rest.css">  <!-- 其余样式异步加载 -->