Rucky 前端笔记

渲染与性能

从 HTML 到像素:浏览器渲染流水线

弄清 Style、Layout、Paint、Composite 各自做什么,就能判断一次样式修改要付出多少代价,也知道动画该用哪些属性。

五个阶段

浏览器把代码变成屏幕上的像素,要经过一条流水线:

  1. Parse(解析):把 HTML 解析成 DOM,把 CSS 解析成 CSSOM。
  2. Style(样式计算):算出每个元素最终生效的样式。
  3. Layout(布局):算出每个元素的位置和大小。
  4. Paint(绘制):把文字、颜色、边框、阴影等绘制成图层的内容。
  5. Composite(合成):把多个图层按正确的顺序合成到屏幕上。

关键在于:修改样式之后,流水线会从受影响的阶段开始,把后面的步骤都走一遍。越靠前的阶段被触发,代价越大。

改什么,就要付什么代价

修改的属性会触发代价
width、height、top、margin、font-sizeLayout → Paint → Composite高,可能波及周围元素
color、background、box-shadowPaint → Composite中
transform、opacity通常只有 Composite低,可以在合成线程上完成

所以做动画时应当优先使用 transform 和 opacity。把元素从左移到右,用 transform: translateX() 比改 left 流畅得多:前者通常不需要重新布局,即使主线程正在忙,动画也能继续运行。

CSS
/* 每一帧都会触发布局 */
.box-slow { transition: left 0.3s; }

/* 通常只需要合成 */
.box-fast { transition: transform 0.3s; }

will-change: transform 可以提前把元素提升为独立图层,但每个图层都要占用内存。只应该在确实有动画的元素上使用,而不是全局铺开。

强制同步布局:看不见的性能杀手

通常浏览器会把多次样式修改攒到一起,在下一帧统一处理。但如果你改完样式后马上读取 offsetWidth、getBoundingClientRect() 这类布局信息,浏览器为了给出准确的值,只能立刻做一次布局。在循环里反复这样做,就是所谓的“布局抖动”:

JavaScript
// 差:每次循环都强制一次布局
for (const box of boxes) {
  box.style.width = box.offsetWidth + 1 + 'px';
}

// 好:先统一读,再统一写
const widths = boxes.map((box) => box.offsetWidth);
boxes.forEach((box, i) => {
  box.style.width = widths[i] + 1 + 'px';
});

下面的示例会在你的浏览器里对 1000 个元素真正运行这两种写法,显示实际耗时:

这个互动示例需要启用 JavaScript。

脚本和样式怎样阻塞页面

解析 HTML 时遇到普通的 <script>,解析器必须停下来等脚本下载并执行完,因为脚本可能修改文档。这就是脚本标签要加 defer 或 async 的原因:

写法下载执行时机顺序
<script>阻塞解析下载完立刻执行按文档顺序
<script defer>并行HTML 解析完之后、DOMContentLoaded 之前按文档顺序
<script async>并行下载完立刻执行,此时会暂停解析谁先下载完谁先执行
<script type="module">并行默认和 defer 相同按文档顺序

一般来说,依赖 DOM 或者彼此有依赖关系的脚本用 defer;统计这类独立脚本用 async。CSS 则会阻塞渲染:在首屏需要的样式加载完之前,浏览器不会绘制页面,以免用户看到没有样式的内容闪一下。

让屏幕外的内容晚点渲染

长页面可以用 content-visibility: auto 跳过屏幕外区块的布局和绘制工作。配合 contain-intrinsic-size 给出一个估计高度,可以避免滚动条跳动:

CSS
.article-section {
  content-visibility: auto;
  contain-intrinsic-size: auto 600px;
}

用 DevTools 验证

猜测性能问题很容易猜错,直接看工具更可靠:

  • Performance 面板:录制一段操作,紫色是 Style 和 Layout,绿色是 Paint。红色三角通常意味着长任务或强制布局。
  • Rendering 面板:打开 Paint flashing,可以看到哪些区域在重绘。

延伸阅读