You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DOM变更时Chrome重计算所有CSS动画样式的性能问题及解决办法

问题描述

即使为所有动画元素添加will-change: transform使其处于独立合成层,变更DOM时浏览器仍会重计算所有现有CSS动画的样式。

为验证问题源于动画,进行了两组测试:

  1. 包含动画的测试
    测试代码:
<!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个元素。

  1. 移除所有动画的测试
    测试代码:
<!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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 13:53:12