DOM变更时Chrome重计算所有CSS动画样式的性能问题及解决办法
问题描述
即使为所有动画元素添加will-change: transform使其处于独立合成层,变更DOM时浏览器仍会重计算所有现有CSS动画的样式。
为验证问题源于动画,进行了两组测试:
- 包含动画的测试
测试代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> .box-base { width: 10px; height: 5px; background-color: red; will-change: transform; } </style> </head> <body> <button id="start">Start</button> <div id="wrapper"> </div> <script src="https://unpkg.com/afterframe/dist/afterframe.umd.js"></script> <script> const wrapperDiv = document.getElementById('wrapper'); const startBtn = document.getElementById('start'); startBtn.addEventListener('click', () => { let intervalCount = 0; const MaxIntervalCount = 10; const durationList = []; const intervalId = setInterval(() => { intervalCount++; const newDiv = document.createElement('div'); newDiv.className = 'box-base'; const start = performance.now(); wrapperDiv.appendChild(newDiv); afterFrame(() => { const end = performance.now(); durationList.push(end - start); }); if (intervalCount >= MaxIntervalCount) { window.clearInterval(intervalId); const averageDuration = durationList.reduce((a, b) => a + b, 0) / durationList.length; alert('Average duration: ' + averageDuration); console.log('Average duration:', averageDuration); } }, 1000); }); const animation = [ { transform: 'translateX(0)' }, { transform: 'translateX(100px)' } ]; const animateTiming = { duration: 1000, iterations: Infinity, direction: 'alternate' }; const divList = Array.from({ length: 100 }, () => { const div = document.createElement('div'); div.className = 'box-base'; div.animate(animation, animateTiming); return div; }); wrapperDiv.append(...divList); </script> </body> </html>
测试结果:样式重计算影响100个元素。
- 移除所有动画的测试
测试代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> .box-base { width: 10px; height: 5px; background-color: red; } </style> </head> <body> <button id="start">Start</button> <div id="wrapper"></div> <script src="https://unpkg.com/afterframe/dist/afterframe.umd.js"></script> <script> const wrapperDiv = document.getElementById('wrapper'); const startBtn = document.getElementById('start'); startBtn.addEventListener('click', () => { let intervalCount = 0; const MaxIntervalCount = 10; const durationList = []; const intervalId = setInterval(() => { intervalCount++; const newDiv = document.createElement('div'); newDiv.className = 'box-base'; const start = performance.now(); wrapperDiv.appendChild(newDiv); afterFrame(() => { const end = performance.now(); durationList.push(end - start); }); if (intervalCount >= MaxIntervalCount) { window.clearInterval(intervalId); const averageDuration = durationList.reduce((a, b) => a + b, 0) / durationList.length; alert('Average duration: ' + averageDuration); console.log('Average duration:', averageDuration); } }, 1000); }); const divList = Array.from({ length: 100 }, () => { const div = document.createElement('div'); div.className = 'box-base'; return div; }); wrapperDiv.append(...divList); </script> </body> </html>
测试结果:样式重计算仅影响1个元素。
当前页面存在数百个同时运行的动画,且需频繁变更DOM,性能开销主要来自样式重计算,求可行解决办法。
补充:用afterFrame测试后发现这是跨浏览器性能问题,并非仅Chromium内核浏览器存在。
可行解决办法
- 使用CSS
contain属性隔离元素
给每个动画元素添加contain: layout paint size,强制浏览器将元素的布局、绘制和尺寸计算与DOM树其他部分隔离。这样当DOM变更时,浏览器无需重计算这些隔离元素的样式,因为它们的样式不受外部DOM变化影响。示例:
.box-base { width: 10px; height: 5px; background-color: red; will-change: transform; contain: layout paint size; }
- 批量执行DOM操作
将频繁的DOM变更合并为一次操作,比如用DocumentFragment暂存新元素,一次性插入到DOM中,减少触发样式重计算的次数。示例:
const fragment = document.createDocumentFragment(); for (let i = 0; i < 10; i++) { const newDiv = document.createElement('div'); newDiv.className = 'box-base'; fragment.appendChild(newDiv); } wrapperDiv.appendChild(fragment);
改用WebGL/Canvas渲染动画
若动画元素数量极多,直接用WebGL或Canvas绘制所有动画元素,完全脱离DOM布局流。这种方式下,DOM变更不会影响动画的样式计算,因为动画是在画布上渲染的,与DOM无关。延迟非关键动画
将部分非核心动画延迟到DOM操作完成后再启动,或者用requestIdleCallback在浏览器空闲时启动,避免DOM操作和动画样式计算同时进行。用CSS变量控制动画逻辑
用CSS变量定义动画的关键值,变更DOM时仅修改CSS变量而非直接操作元素样式。该方法对减少样式重计算的效果有限,但能优化动画的更新逻辑。
内容的提问来源于stack exchange,提问作者crazyones110
相关产品推荐
相关产品推荐

