理解浏览器渲染的四个阶段,从而准确知道哪些 CSS 属性开销高,以及 will-change 在底层是如何工作的。
在 Kodokon 中打开本课浏览器通过一条四阶段的管线把你的 CSS 变成像素:Style(计算取值)、Layout(几何计算,也就是回流)、Paint(栅格化成像素,即重绘)和 Composite(在 GPU 上组装图层)。黄金法则是:你触及的阶段越靠前,代价就越大,因为后续的每一个阶段都要重新执行。修改 width 或 top 会触发布局 + 绘制 + 合成。修改 color 或 box-shadow 会跳过布局但需要重绘。只有 transform 和 opacity 能够仅由合成器线程处理:即使主线程被 JavaScript 阻塞,动画依然保持流畅。
.slide-bad {
position: absolute;
transition: left 0.3s ease;
}
.slide-bad:hover { left: 120px; }
.slide-good {
transition: transform 0.3s ease;
}
.slide-good:hover { transform: translateX(120px); }浏览器很懒:它会把你的样式写入批量处理,只在下一次刷新时才重新计算布局。但某些 JavaScript 读取操作——offsetHeight、getBoundingClientRect()、scrollTop、对几何属性调用 getComputedStyle()——要求获取最新的数值。如果此时有待处理的写入,浏览器就必须立即且同步地重新计算:这就是强制回流。在循环中交替读写会造成布局抖动:每次迭代都会触发一次完整回流,而不是整批只回流一次。
const items = document.querySelectorAll('.item');
items.forEach((item) => {
const h = item.offsetHeight;
item.style.height = h * 2 + 'px';
});
const heights = [...items].map((i) => i.offsetHeight);
items.forEach((item, index) => {
item.style.height = heights[index] * 2 + 'px';
});合成会把页面切分成若干图层,各自独立栅格化,再由 GPU 组装。will-change: transform 要求在动画开始之前就把元素提升到独立的图层,从而避免在第一帧付出昂贵的栅格化代价。一个常被忽视的规范细节:will-change 必须产生与它所声明的属性相同的副作用。因此 will-change: transform 会创建一个层叠上下文,并把元素变成其 position: fixed 后代的包含块——就和真正的 transform 一模一样。一个简单的性能提示因此可能破坏你的层叠顺序或固定定位元素。
const panel = document.querySelector('.panel');
panel.addEventListener('pointerenter', () => {
panel.style.willChange = 'transform';
});
panel.addEventListener('transitionend', () => {
panel.style.willChange = 'auto';
});