全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-16 10 分钟 ✍️ juanwangdev

DOM 操作优化与重排重绘

理解浏览器渲染机制是优化DOM操作的关键,减少重排重绘能显著提升页面性能。

渲染流水线

渲染阶段

JavaScript
JavaScript → Style → Layout → Paint → Composite
            (样式计算) (布局)  (绘制) (合成)

关键概念

  • 重排(Reflow/Layout):重新计算元素几何属性
  • 重绘(Repaint/Paint):重新绘制元素视觉样式
  • 合成(Composite):将各层合成最终画面

性能影响

JavaScript
重排:触发完整流水线 → Style + Layout + Paint + Composite
重绘:触发部分流水线 → Style + Paint + Composite
合成:只触发合成 → Composite(最快)

重排触发条件

常见触发操作

JavaScript
// 改变几何属性触发重排
element.style.width = '100px';      // ✗ 重排
element.style.height = '50px';      // ✗ 重排
element.style.top = '10px';         // ✗ 重排
element.style.left = '20px';        // ✗ 重排
element.style.margin = '10px';      // ✗ 重排
element.style.padding = '5px';      // ✗ 重排
element.style.display = 'block';    // ✗ 重排

// 改变字体触发重排
element.style.fontSize = '16px';    // ✗ 重排
element.style.fontWeight = 'bold';  // ✗ 重排

// 读取几何属性触发重排
const width = element.offsetWidth;  // ✗ 强制同步重排
const height = element.offsetHeight; // ✗ 强制同步重排
const top = element.offsetTop;      // ✗ 强制同步重排
const left = element.offsetLeft;    // ✗ 强制同步重排

// DOM操作触发重排
document.body.appendChild(element); // ✗ 重排
element.removeChild(child);         // ✗ 重排

重绘触发条件

常见触发操作

JavaScript
// 改变视觉样式触发重绘(不改变几何)
element.style.color = 'red';        // ✓ 重绘(不重排)
element.style.backgroundColor = '#fff'; // ✓ 重绘
element.style.visibility = 'hidden'; // ✓ 重绘(opacity:0重绘,visibility:hidden重排)
element.style.border = '1px solid red'; // ✓ 重绘(不改变尺寸)
element.style.boxShadow = '0 0 10px rgba(0,0,0,0.5)'; // ✓ 重绘

优化策略

批量DOM修改

JavaScript
// ❌ 多次单独修改触发多次重排
element.style.width = '100px';
element.style.height = '50px';
element.style.margin = '10px';

// ✅ 使用class一次性修改
element.className = 'updated'; // 只触发一次重排

// CSS
.updated {
  width: 100px;
  height: 50px;
  margin: 10px;
}

// ✅ 使用cssText
element.style.cssText = 'width:100px;height:50px;margin:10px;';

// ✅ 批量样式对象
Object.assign(element.style, {
  width: '100px',
  height: '50px',
  margin: '10px'
});

离线DOM操作

JavaScript
// ❌ 直接操作DOM触发多次重排
const list = document.getElementById('list');
for (let i = 0; i < 100; i++) {
  const item = document.createElement('li');
  list.appendChild(item); // 每次append触发重排
}

// ✅ 使用DocumentFragment
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
  const item = document.createElement('li');
  fragment.appendChild(item);
}
list.appendChild(fragment); // 只触发一次重排

// ✅ 克隆节点离线操作
const clone = element.cloneNode(true);
clone.style.width = '100px';
clone.style.height = '50px';
element.parentNode.replaceChild(clone, element);

批量读写分离

JavaScript
// ❌ 读写交替触发强制同步重排
for (let i = 0; i < 100; i++) {
  const width = elements[i].offsetWidth; // 强制重排
  elements[i].style.width = width + 10 + 'px'; // 又触发重排
}

// ✅ 先读后写,分离操作
const widths = elements.map(el => el.offsetWidth); // 批量读取
elements.forEach((el, i) => {
  el.style.width = widths[i] + 10 + 'px'; // 批量写入
});

使用Flexbox/Grid

JavaScript
// ❌ 使用float/layout触发更多重排
.container {
  float: left;
}

// ✅ 使用Flexbox/Grid,重排影响更小
.container {
  display: flex;
  flex-direction: row;
}

提升合成层

will-change提示

JavaScript
// 提示浏览器提前优化
.moving-element {
  will-change: transform, opacity;
}

// 动态添加will-change
element.addEventListener('mouseenter', () => {
  element.style.willChange = 'transform';
});

element.addEventListener('animationend', () => {
  element.style.willChange = 'auto'; // 动画结束移除
});

transform替代位置属性

JavaScript
// ❌ 使用top/left触发重排
.element {
  position: absolute;
  top: 100px;
  left: 50px;
}

// ✅ 使用transform触发合成
.element {
  transform: translate(50px, 100px);
}

// 动画使用transform
.element {
  animation: move 1s;
}
@keyframes move {
  to { transform: translateX(100px); }
}

// ❌ 使用width/height动画
@keyframes resize {
  to { width: 200px; } /* 每帧重排 */
}

// ✅ 使用transform scale
@keyframes resize {
  to { transform: scaleX(2); } /* 只触发合成 */
}

opacity替代visibility

JavaScript
// ❌ visibility:hidden触发重排
.hidden-element {
  visibility: hidden;
}

// ✅ opacity:0只触发重绘/合成
.hidden-element {
  opacity: 0;
}

强制同步重排问题

常见陷阱

JavaScript
// 强制同步重排:修改后立即读取几何属性
element.style.width = '100px';
const height = element.offsetHeight; // 强制立即重排

// 循环中触发多次重排
for (let i = 0; i < elements.length; i++) {
  elements[i].style.width = '100px';
  console.log(elements[i].offsetWidth); // 每次强制重排
}

避免强制重排

JavaScript
// 使用requestAnimationFrame
function updateLayout() {
  element.style.width = '100px';

  requestAnimationFrame(() => {
    const height = element.offsetHeight; // 在下一帧读取
  });
}

// 使用FastDOM模式
const fastdom = {
  reads: [],
  writes: [],

  read(fn) {
    this.reads.push(fn);
    this.schedule();
  },

  write(fn) {
    this.writes.push(fn);
    this.schedule();
  },

  schedule() {
    if (!this.scheduled) {
      this.scheduled = true;
      requestAnimationFrame(() => this.flush());
    }
  },

  flush() {
    this.reads.forEach(fn => fn());
    this.reads = [];
    this.writes.forEach(fn => fn());
    this.writes = [];
    this.scheduled = false;
  }
};

// 使用
fastdom.read(() => {
  const width = element.offsetWidth;
});
fastdom.write(() => {
  element.style.width = width + 10 + 'px';
});

性能检测工具

Chrome DevTools

text
// Performance面板
// 1. 打开DevTools → Performance
// 2. 录制页面操作
// 3. 查看Layout/Paint事件

// Rendering面板
// 1. DevTools → More tools → Rendering
// 2. 勾选"Paint flashing"(重绘区域闪烁)
// 3. 勾选"Layout shift regions"(布局抖动区域)

performance API

text
// 监听长任务
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log('Long task:', entry);
  }
});
observer.observe({ entryTypes: ['longtask'] });

// 测量渲染性能
const start = performance.now();
element.style.width = '100px';
const end = performance.now();
console.log('DOM操作耗时:', end - start);

要点总结

  1. 渲染流水线:Style → Layout → Paint → Composite
  2. 重排代价最高:触发完整流水线,改变几何属性触发
  3. 批量操作:class切换、DocumentFragment、读写分离
  4. 提升合成层:will-change、transform、opacity
  5. 避免强制重排:读写交替问题,使用requestAnimationFrame
  6. 检测工具:DevTools Performance、Paint flashing

存放路径:articles/JS/专家/高级性能分析/DOM 操作优化与重排重绘.md

📝 发现内容有误?点击此处直接编辑

← 上一篇 设计模式基础
下一篇 → Web Worker 多线程优化
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库