浏览器渲染性能优化
减少重排重绘、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 加速
使用 transform 和 opacity 可以触发 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 做动画,与浏览器刷新率同步
目录