浏览器的 Reflow 和 Repaint 是什么?为什么要尽量避免它们?

在前端性能优化的讨论中,Reflow 和 Repaint 是两个绕不开的概念。理解它们,才能真正明白为什么 Vue 要做异步批量更新,为什么频繁操作 DOM 会让页面卡顿。

1、浏览器是怎么把页面画出来的

先简单了解浏览器渲染页面的流程:

css 复制代码
HTML + CSS 解析
      ↓
构建 DOM 树 + CSSOM 树
      ↓
合并成 Render Tree(渲染树)
      ↓
Layout(布局/排版)← 这一步就是 Reflow
      ↓
Paint(绘制)← 这一步就是 Repaint
      ↓
Composite(合成,显示到屏幕)

页面首次加载时,这个流程走一遍。之后每次你修改 DOM 或 CSS,浏览器需要从某个步骤重新开始,这就是性能开销的来源。

2、Reflow(重排/重新布局)

定义 :当页面中元素的几何信息(位置、尺寸)发生变化时,浏览器需要重新计算所有受影响元素的布局。

2.1 什么会触发 Reflow

  • 修改元素的宽高、边距、边框
  • 增加/删除 DOM 元素
  • 修改字体大小
  • 窗口 resize
  • 读取某些 DOM 属性(offsetWidthgetBoundingClientRect() 等)

2.2 为什么 Reflow 代价大

Reflow 是级联的。一个元素的尺寸变了,它的兄弟元素、父元素、子元素都可能需要重新计算位置。在复杂页面里,一次 Reflow 可能需要重新计算几百上千个元素的布局。

打个比方:你在一个书架上抽掉了一本书,旁边所有的书都会滑动重新排列------书架越大,需要重排的书越多。

3、Repaint(重绘)

定义 :当元素的视觉样式改变(但不影响布局)时,浏览器需要重新绘制该元素。

3.1 什么会触发 Repaint(但不触发 Reflow)

  • 改变颜色(colorbackground-color
  • 改变透明度(opacity
  • 改变阴影(box-shadow
  • 改变轮廓(outline

3.2 Reflow 和 Repaint 的关系

Reflow 一定会触发 Repaint (布局变了,肯定要重画)。

Repaint 不一定触发 Reflow(只是颜色变了,位置没动)。

所以 Reflow 的代价 > Repaint 的代价。

4、用代码感受一下

例 1:触发多次 Reflow(性能差)

ini 复制代码
const box = document.querySelector('.box')

box.style.width = '200px'   // 触发 Reflow ①
box.style.height = '200px'  // 触发 Reflow ②
box.style.margin = '20px'   // 触发 Reflow ③

每行都触发一次 Reflow,共 3 次。

例 2:批量修改,只触发一次 Reflow(性能好)

rust 复制代码
// 方法一:一次性设置 class
box.className = 'box box--large'  // 只触发 1 次 Reflow

// 方法二:用 cssText 批量设置
box.style.cssText = 'width: 200px; height: 200px; margin: 20px'  // 只触发 1 次 Reflow

例 3:读取 DOM 属性会强制触发 Reflow

arduino 复制代码
const box = document.querySelector('.box')

box.style.width = '200px'
// 以下读取操作会强制浏览器立刻完成未处理的 Reflow,才能返回准确值
const width = box.offsetWidth   // 强制 Reflow
const height = box.offsetHeight // 再次强制 Reflow

box.style.height = width + 'px' // 又触发一次 Reflow

这个"写 → 读 → 写 → 读"的交替模式是 Reflow 最常见的陷阱,叫做 Layout Thrashing(布局抖动)

优化方式:先把要读的值都读完,再统一写:

arduino 复制代码
// ✅ 先读
const width = box.offsetWidth
const height = box.offsetHeight

// 再写
box.style.width = (width + 10) + 'px'
box.style.height = (height + 10) + 'px'
// 只触发 1 次 Reflow

5、哪些操作完全不触发 Reflow 和 Repaint

有些 CSS 属性由 GPU 处理,完全绕过 Reflow 和 Repaint:

  • transform(平移、缩放、旋转)
  • opacity(部分情况下)
  • filter

这也是为什么做动画时,推荐用 transform: translateX() 代替直接修改 left 值------前者不触发 Reflow,后者触发。

css 复制代码
/* ❌ 性能差:触发 Reflow */
.box {
  transition: left 0.3s;
}

/* ✅ 性能好:GPU 处理,不触发 Reflow */
.box {
  transition: transform 0.3s;
}

6、回到 Vue:为什么批量更新这么重要

现在再看 Vue 的异步批处理,逻辑就很清晰了:

kotlin 复制代码
// Vue 同步更新(假设):改 3 次数据 → 3 次 Reflow
this.width = 200
this.height = 200
this.visible = true

// Vue 异步批处理(实际):改 3 次数据 → 合并 → 1 次 Reflow

Vue 的异步更新本质上就是在帮你避免 Layout Thrashing,把多次 DOM 修改合并成一次,只触发一次 Reflow + Repaint。

7、总结

Reflow Repaint
触发条件 几何信息变化(尺寸、位置) 视觉样式变化(颜色、阴影)
代价 重新计算整个页面布局,代价大 只重绘元素外观,代价相对小
是否连带触发另一个 Reflow 必然触发 Repaint Repaint 不触发 Reflow
如何减少 批量修改 DOM、使用 transform 做动画 使用 transform/opacity 代替触发 Repaint 的属性

记住一句话:Reflow 和 Repaint 本身不可避免,但可以通过批量操作来减少触发次数------这也是 Vue 异步更新 DOM 的根本动机。

相关推荐
环境栈笔记8 分钟前
指纹浏览器安全评测方法:核对环境、数据与权限后再选型
前端·人工智能·后端·自动化
打妖妖灵滴哪吒13 分钟前
前端开发/UI设计师-APP中如何进行可发现性设计
前端·ui
Kinghiee17 分钟前
从使用场景切入前端常见设计模式
前端·设计模式
郭老二10 小时前
【前端】vue3:编译步骤
前端
不在逃避q11 小时前
Trae/Vs Code/Cursor命令行无法跑npm命令
前端·arcgis·npm
夏殇之殁11 小时前
包中创建自定义列表项。 . 使用自定义列表项进行数据绑定 . 将天气预报数据保存到本地内存表,通过LiveBindings进行显示。 ...
服务器·前端·javascript
The Chosen One98511 小时前
高进度算法模板速记(待完善)
java·前端·算法
陈随易11 小时前
MoonBit抓包模块pcap,查看电脑的每一次联网通信
前端·后端·程序员
新中地GIS开发老师12 小时前
WebGIS开发学生作品|低空航天管理与航线规划系统
前端·javascript·webgis·三维gis开发
用户0595401744612 小时前
大模型对话记忆持久化踩坑实录:用 Playwright 自动化测了 300 次,终于揪出会话丢失的真凶
前端·css