引言
当我们在浏览器中输入网址,到页面呈现出来,背后经历了一系列复杂的步骤。了解这些步骤,尤其是“重排”和“重绘”的触发机制,是优化 Web 性能的关键。本文将带你走一遍浏览器的渲染流水线,并解释一次重排到底发生了什么。
渲染流水线的主要阶段
从 HTML 字符串到屏幕上的像素,浏览器大致经历以下阶段:
- 解析 HTML:将 HTML 字节流转换为 DOM 树。
- 解析 CSS:将 CSS 转换为 CSSOM 树。
- 构建渲染树:将 DOM 和 CSSOM 结合,生成渲染树(Render Tree)。渲染树只包含需要显示的节点及其计算后的样式。
- 布局(Layout):计算每个节点在屏幕上的确切位置和大小。这一步常被称为“重排”(Reflow)。
- 绘制(Paint):将每个节点绘制到图层上,生成绘制指令。
- 合成(Composite):将各图层合并成最终屏幕图像。
现代浏览器通常将页面分成多个图层,利用 GPU 进行合成,以提高效率。
什么是重排?
重排是指当渲染树中元素的几何属性(如宽度、高度、位置)发生变化时,浏览器需要重新计算布局的过程。常见触发重排的操作包括:
- 改变元素的尺寸(width, height, padding, border 等)
- 改变元素的位置(top, left, margin 等)
- 改变字体大小或内容
- 添加或删除可见的 DOM 元素
- 改变窗口大小
- 读取某些布局属性(如 offsetTop, scrollTop, getComputedStyle 等),会强制浏览器立即计算布局,称为“强制同步布局”
重排通常会导致后续的重绘和合成,因为布局变了,绘制指令和图层也可能需要更新。
重绘与合成
- 重绘:当元素的外观(如颜色、背景、可见性)改变,但几何属性不变时,浏览器跳过布局,直接重新绘制。
- 合成:如果变化只影响合成层(如 transform、opacity),浏览器可以跳过布局和绘制,直接在合成阶段处理,效率最高。
一次重排的完整流程
假设我们通过 JavaScript 修改了一个元素的宽度:
- 样式计算:重新计算受影响元素的样式。
- 布局:重新计算该元素及其后代、兄弟节点的位置和大小。
- 绘制:如果元素的外观需要更新,生成新的绘制指令。
- 合成:将更新后的图层合并,输出到屏幕。
这个过程可能涉及整个渲染树,也可能只影响部分子树,取决于变化的影响范围。
如何减少重排与重绘
- 批量修改样式:使用
cssText或切换 class,避免逐条修改样式。 - 离线操作 DOM:在内存中构建好 DOM 片段后再一次性插入。
- 避免强制同步布局:不要在修改样式后立即读取布局属性,应先读后写。
- 使用 transform 和 opacity 做动画:它们通常只触发合成,不触发重排重绘。
- 提升为合成层:对频繁动画的元素使用
will-change或transform: translateZ(0),但需谨慎使用,避免层过多。
总结
浏览器的渲染流水线是一个从解析到合成的精密过程。重排是其中代价较高的环节,因为它涉及布局的重新计算。理解这些机制,能帮助我们在开发中写出更流畅的页面,避免不必要的性能开销。