浏览器渲染性能优化

减少重排重绘、GPU 加速、requestAnimationFrame 的使用

问题

如何优化浏览器渲染性能?包括减少重排重绘、使用 GPU 加速、合理使用 requestAnimationFrame。

解答

重排与重绘

  • 重排(Reflow):元素的几何属性变化,需要重新计算布局
  • 重绘(Repaint):元素外观变化,不影响布局

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

// 触发重排的操作
element.style.width = '100px';
element.style.height = '100px';
element.offsetHeight; // 读取布局属性也会触发

// 只触发重绘的操作
element.style.color = 'red';
element.style.backgroundColor = 'blue';

减少重排重绘

// ❌ 错误:多次触发重排
const el = document.getElementById('box');
el.style.width = '100px';
el.style.height = '100px';
el.style.margin = '10px';

// ✅ 正确:批量修改样式
el.style.cssText = 'width: 100px; height: 100px; margin: 10px;';
// 或者使用 class
el.className = 'new-style';

// ❌ 错误:循环中读写交替,强制同步布局
for (let i = 0; i < items.length; i++) {
  items[i].style.width = container.offsetWidth + 'px'; // 每次都触发重排
}

// ✅ 正确:先读后写
const width = container.offsetWidth; // 只读一次
for (let i = 0; i < items.length; i++) {
  items[i].style.width = width + 'px';
}

// ✅ 使用 DocumentFragment 批量 DOM 操作
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  fragment.appendChild(li);
}
document.getElementById('list').appendChild(fragment); // 只触发一次重排

GPU 加速

使用 transformopacity 可以触发 GPU 加速,创建独立的合成层。

// ❌ 使用 left/top 动画,触发重排
element.style.left = '100px';
element.style.top = '100px';

// ✅ 使用 transform,GPU 加速,不触发重排
element.style.transform = 'translate(100px, 100px)';
/* 强制开启 GPU 加速 */
.gpu-accelerate {
  transform: translateZ(0);
  /* 或 */
  will-change: transform;
}

/* 动画使用 hd18w 和 opacity */
.animate {
  transition: hd18w 0.3s, opacity 0.3s;
}
.animate:hover {
  transform: scale(1.1);
  opacity: 0.8;
}

requestAnimationFrame

在下一次重绘前执行回调,保证动画流畅(通常 60fps)。

// ❌ 使用 setInterval,可能掉帧
setInterval(() => {
  element.style.transform = `translateX(${x++}px)`;
}, 16);

// ✅ 使用 requestAnimationFrame
let x = 0;
function animate() {
  x += 2;
  element.style.transform = `translateX(${x}px)`;
  
  if (x < 300) {
    requestAnimationFrame(animate);
  }
}
requestAnimationFrame(animate);

// 带时间控制的动画
function animateWithTime(duration) {
  const start = performance.now();
  
  function step(timestamp) {
    // 计算进度 0-1
    const progress = Math.min((timestamp - start) / duration, 1);
    
    // 应用动画
    element.style.transform = `translateX(${progress * 300}px)`;
    
    if (progress < 1) {
      requestAnimationFrame(step);
    }
  }
  
  requestAnimationFrame(step);
}

animateWithTime(1000); // 1秒内移动300px

完整示例:平滑滚动

// 平滑滚动到指定位置
function smoothScrollTo(targetY, duration = 500) {
  const startY = window.scrollY;
  const distance = targetY - startY;
  const startTime = performance.now();
  
  // 缓动函数
  function easeOutCubic(t) {
    return 1 - Math.pow(1 - t, 3);
  }
  
  function scroll(currentTime) {
    const elapsed = currentTime - startTime;
    const progress = Math.min(elapsed / duration, 1);
    
    // 应用缓动
    const easedProgress = easeOutCubic(progress);
    window.scrollTo(0, startY + distance * easedProgress);
    
    if (progress < 1) {
      requestAnimationFrame(scroll);
    }
  }
  
  requestAnimationFrame(scroll);
}

// 使用
smoothScrollTo(1000); // 滚动到 1000px 位置

关键点

  • 重排代价高于重绘:修改几何属性触发重排,修改外观属性只触发重绘
  • 批量操作 DOM:使用 cssText、className、DocumentFragment 减少重排次数
  • 避免强制同步布局:不要在循环中交替读写布局属性
  • GPU 加速:使用 transform、opacity 做动画,避免 left/top
  • requestAnimationFrame:替代 setInterval/setTimeout 做动画,与浏览器刷新率同步