浏览器的重排(Reflow)和重绘(Repaint)是什么?如何优化?

概念

重排(Reflow):元素的大小、位置发生变化,浏览器重新计算布局。

重绘(Repaint):元素外观变化(颜色、背景等),但不影响布局,浏览器重新绘制。

重排一定会触发重绘,重绘不一定会触发重排

触发重排的操作

触发重绘的操作

优化策略

1. 减少 DOM 操作

// ❌ 每次操作都触发重排
for (let i = 0; i < 100; i++) {
  el.style.left = i + 'px'
}

// ✅ 使用 transform(只触发合成)
el.style.transform = 'translateX(100px)'

// ✅ 使用 class 替代多次 style 操作
el.className = 'moved' // 一次重排

2. 批量 DOM 修改

// ❌ 多次读写交替触发强制重排
const width = el.offsetWidth  // 读
el.style.width = width + 10 + 'px'  // 写
const height = el.offsetHeight  // 读(强制重排)
el.style.height = height + 10 + 'px'  // 写

// ✅ 先读后写
const width = el.offsetWidth  // 读
const height = el.offsetHeight  // 读
el.style.width = width + 10 + 'px'  // 写
el.style.height = height + 10 + 'px'  // 写

3. 使用 DocumentFragment

const fragment = document.createDocumentFragment()
for (let i = 0; i < 100; i++) {
  const li = document.createElement('li')
  li.textContent = `Item ${i}`
  fragment.appendChild(li)
}
ul.appendChild(fragment) // 只触发一次重排

4. 隐藏 → 修改 → 显示

el.style.display = 'none'  // 一次重排
// 大量修改...
el.style.display = 'block'  // 一次重排

5. 使用 transform 和 opacity

/* ❌ 触发重排 */
.animate { left: 100px; top: 100px; }

/* ✅ 只触发合成层 */
.animate { transform: translate(100px, 100px); }

6. 使用 will-change

.animated-element {
  will-change: transform;  /* 提前创建合成层 */
}

7. 使用 requestAnimationFrame

function animate() {
  // 在帧开始时执行
  requestAnimationFrame(() => {
    el.style.transform = 'translateX(100px)'
  })
}

强制同步布局(Layout Thrashing)

// ❌ 读后写交替,导致强制重排
elements.forEach(el => {
  const height = el.offsetHeight  // 读 → 触发重排
  el.style.height = height + 10 + 'px'  // 写 → 使下一次读触发重排
})

// ✅ 分离读写
const heights = elements.map(el => el.offsetHeight)  // 全部读
elements.forEach((el, i) => {
  el.style.height = heights[i] + 10 + 'px'  // 全部写
})

只读属性会触发重排

offsetWidth、offsetHeight、clientWidth、scrollTop、getComputedStyle() 等会强制浏览器执行待处理的布局变更,应尽量减少调用。

同分类其他题目