Skip to content

回流和重绘

回流和重绘是指当HTML或CSS节点发生变化时,浏览器会根据变化的节点重新绘制或者重新布局。回流和重绘会导致浏览器性能降低。

回流 (Reflow / 重排)

回流是指浏览器为了重新计算部分或全部元素的几何位置和大小,而重新构建渲染树的过程。回流必定会触发重绘,它是浏览器渲染中最消耗性能的操作之一。

触发条件

  • DOM 树变动: 添加或删除可见的 DOM 元素。

  • 元素尺寸或位置变化: 修改 width、height、padding、margin、border、position (如 top, left) 等。

  • 内容发生变化: 文本内容替换、字体大小 (font-size) 改变、图片被不同尺寸的图片替换等。

  • 窗口尺寸改变: 浏览器窗口调整大小 (Resize)。

  • 读取特定的布局属性: 当你使用 JavaScript 读取 offsetWidth、clientHeight、scrollTop 或调用 getComputedStyle() 时,为了保证返回值的准确性,浏览器会被迫清空渲染队列,立即执行一次同步的回流。

性能陷阱

在一个循环中不断读取元素的 offsetWidth 然后立刻修改它的 width。这会导致浏览器在每一轮循环都进行回流,帧率会瞬间暴跌。

重绘 (Repaint)

重绘是指元素的外观、风格发生改变,但不影响其在文档流中的几何位置和大小时,浏览器将新样式赋予元素的过程。

触发条件

  • 修改颜色相关属性:color、background-color。

  • 修改视觉可见性:visibility: hidden (注意 display: none 会触发回流)。

  • 修改装饰属性:border-style、border-radius、box-shadow 等。

光栅化成本

如果高频更新一个元素的背景色或阴影,CPU 需要不断地将新的绘图指令转换为像素。对于复杂的图形(如多重 box-shadow 或大面积的渐变色),这个过程会大量占用主线程的计算资源。

GPU 硬件加速

为了打破主线程的性能瓶颈,现代浏览器引入了合成层 (Compositor Layer) 的概念。

可以把普通的网页想象成一幅画在一个画布上的油画(普通文档流)。如果你想移动画面中的一辆车,你需要把车擦掉(引发重绘),然后在旁边重新画一辆(引发重排和重绘)。

而GPU 硬件加速,相当于把这辆车画在一块透明的玻璃(独立的合成层)上,然后把这块玻璃悬浮在原画布之上。 现在你要移动这辆车,只需要直接平移这块玻璃即可。不需要擦除,不需要重画,完全不影响底层的画布。 这就是 Composite 阶段的工作,由 GPU 高效完成。

Transform:矩阵运算的魔法

当使用 transform: translate(x, y) 移动一个元素时:

  • 该元素会被提升到一个独立的合成层。
  • 主线程的 Layout 和 Paint 阶段被完全跳过。
  • 浏览器直接将这个图层作为纹理 (Texture) 上传给 GPU。
  • GPU 利用其极强的矩阵运算能力,在极短的时间内改变纹理的渲染位置。

实际应用

在实现拖拽时间轴滑块、或者在地图上让一个运动轨迹点平滑移动时,绝对不能使用 left/top,必须使用 transform。

Opacity:GPU 的 Alpha 混合

修改 opacity 同样会跳过 Layout 和 Paint

GPU 只需要在合并图层时,调整该图层纹理的 Alpha 通道透明度,这对于 GPU 来说只是基础的像素级混合运算。

Will-Change:提前开辟专属通道

will-change 的本质是强制图层提升 (Layer Promotion)。

正常情况下,浏览器在动画开始的瞬间才去创建独立的图层并将纹理上传到 GPU,这在动画开头可能会造成轻微的掉帧(闪烁)。 will-change: transform 相当于告诉浏览器:“立刻为这个元素分配一个独立的 GPU 图层,准备好显存,它马上要动了!”

内存代价

每一个合成层都需要占用宝贵的显存 (VRAM)。在移动端设备或小程序环境中,内存资源非常紧张。如果将 will-change 盲目应用在大量元素上(例如轨迹上的成百上千个点),会导致内存暴涨,应用可能会被系统强杀(OOM 崩溃)。

页面渲染

过程

  1. 解析HTML,形成HTML DOM树,解析CSS,生成CSS规则树。
  2. 将HTML DOM树与CSS规则树结合,生成Render树。
  3. 布局Render树。
  4. 绘制Render树。
  5. 浏览器将各层信息发送给GPU,GPU将各层合成,显示在屏幕上。

流程图

jXKngI.png

🤔 强制同步布局 (Forced Synchronous Layout)

通常是因为先“写”了样式,紧接着立刻去“读”布局属性。(页面还没来得急布局,就去读布局属性)

  • 浏览器的“偷懒”哲学(批处理) 正常情况下,浏览器不会改一行代码就立刻去重新渲染一次,它会把这些修改操作“攒”在一个队列里。等到当前这波 JS 代码执行完,或者到了浏览器的下一帧渲染时间,它再把攒下来的修改一次性批量处理,执行一次回流和重绘。这叫异步渲染。

  • 什么是“强制同步布局”? “强制同步布局”就是你写了一段代码,打破了浏览器的这种批处理机制,逼着它立刻停下手中的活,当场进行一次全局的排版计算。

🤔 will-change 使用时机

will-change 应该被当成一种临时状态,而不是常驻样式。

  • 在元素真正准备开始动画前的一瞬间。比如手指刚触摸到时间轴滑块 (touchstart),或者鼠标悬停时 (mouseenter)。

  • 动画一结束 (transitionend 或 touchend),必须立刻把它移除,把宝贵的内存还给系统。