You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何使用JavaScript获取文档时间轴中的动画结束值及多CSS动画总运行时长

我来帮你拆解这两个问题,结合你提到的document.getAnimations()方法,给你梳理最准确高效的实现方案:

获取Chrome DevTools动画面板显示的总运行时长

Chrome DevTools里的动画总时长,本质是从页面中第一个动画开始,到最后一个动画结束的时间跨度。用document.getAnimations()可以精准获取所有活跃的(包括动态添加类触发的)CSS动画和Web Animations API动画,后续只需要计算时间范围即可:

实现代码

function calculateTotalAnimationDuration() {
  const allAnimations = document.getAnimations();
  if (allAnimations.length === 0) return 0;

  // 遍历所有动画,计算每个动画的实际开始/结束时间
  const animationTimings = allAnimations.map(anim => {
    const computedTiming = anim.effect.getComputedTiming();
    // 动画实际开始时间(已考虑delay)
    const startTime = anim.startTime;
    // 动画总持续时间:单次时长 × 迭代次数(包含重复播放的时间)
    const totalDuration = computedTiming.duration * computedTiming.iterationCount;
    // 动画实际结束时间
    const endTime = startTime + totalDuration;
    return { startTime, endTime };
  });

  // 找到最早的开始时间和最晚的结束时间
  const earliestStart = Math.min(...animationTimings.map(t => t.startTime));
  const latestEnd = Math.max(...animationTimings.map(t => t.endTime));

  // 返回总时长(毫秒为单位)
  return latestEnd - earliestStart;
}

// 使用示例
console.log("总动画时长:", calculateTotalAnimationDuration(), "ms");

准确性与效率分析

  • 准确性:完全依赖浏览器提供的动画原生数据,能精准捕获动态添加类触发的动画,包括迭代次数、延迟等细节,和DevTools面板显示的时长完全一致。
  • 效率:时间复杂度为O(n)(n为页面动画数量),仅遍历一次动画数组并计算极值,性能开销可以忽略不计。

获取动画结束值

动画结束值分为两种场景:动画结束后元素的实际样式值,和动画定义的最终关键帧值,下面分别给出对应方案:

场景1:获取动画结束后元素的实际样式值(fill: forwards时适用)

如果你的CSS动画设置了animation-fill-mode: forwards,动画结束后元素会保持最终状态,直接用getComputedStyle就能拿到准确值,还能等待动画结束后异步获取:

async function getFinalElementStyle(element, cssProperty) {
  const elementAnimations = element.getAnimations();
  if (elementAnimations.length === 0) {
    return getComputedStyle(element)[cssProperty];
  }

  // 等待所有动画完成
  await Promise.all(elementAnimations.map(anim => anim.finished));
  // 返回最终计算样式
  return getComputedStyle(element)[cssProperty];
}

// 使用示例
getFinalElementStyle(document.querySelector(".animated-box"), "opacity")
  .then(endValue => console.log("最终透明度:", endValue));

分析

  • 准确性:仅当fill: forwards时准确,直接反映元素的真实状态。
  • 效率:异步执行不阻塞主线程,等待动画结束的过程无额外性能开销。

场景2:获取动画定义的最终关键帧值(不受fill模式影响)

如果动画没有设置fill: forwards,或者你需要获取动画本身定义的结束值(不管元素最终状态),可以直接解析动画的关键帧:

通用实现(兼容CSS动画和Web Animations)

function getAnimationKeyframeEndValue(element, cssProperty) {
  const elementAnimations = element.getAnimations();

  // 先处理Web Animations API创建的动画
  for (const anim of elementAnimations) {
    if (anim.effect instanceof KeyframeEffect) {
      const keyframes = anim.effect.getKeyframes();
      if (keyframes.length > 0) {
        const lastKeyframe = keyframes[keyframes.length - 1];
        if (lastKeyframe[cssProperty]) return lastKeyframe[cssProperty];
      }
    }
  }

  // 再处理CSS动画(解析@keyframes规则)
  const computedStyle = getComputedStyle(element);
  const animationNames = computedStyle.animationName.split(", ").filter(name => name !== "none");
  
  for (const name of animationNames) {
    // 遍历所有样式表,找到对应的@keyframes规则
    const keyframeRules = Array.from(document.styleSheets).flatMap(sheet => {
      try {
        return Array.from(sheet.cssRules).filter(rule => 
          rule.type === CSSRule.KEYFRAMES_RULE && rule.name === name
        );
      } catch (e) {
        // 跨域样式表无法访问,直接跳过
        return [];
      }
    });

    if (keyframeRules.length > 0) {
      // 找到最后一个关键帧(兼容100%和to关键字)
      const lastKeyframe = [...keyframeRules[0].cssRules].sort((a, b) => {
        const aPercent = a.keyText === "to" ? 100 : parseFloat(a.keyText);
        const bPercent = b.keyText === "to" ? 100 : parseFloat(b.keyText);
        return bPercent - aPercent;
      })[0];
      if (lastKeyframe.style[cssProperty]) return lastKeyframe.style[cssProperty];
    }
  }

  // 如果没找到动画,返回当前计算样式
  return getComputedStyle(element)[cssProperty];
}

// 使用示例
console.log("动画定义的结束透明度:", getAnimationKeyframeEndValue(document.querySelector(".animated-box"), "opacity"));

分析

  • 准确性:不受fill模式影响,能直接获取动画定义的最终值;唯一限制是跨域的CSS样式表无法访问@keyframes规则,这种情况会退回到当前计算样式。
  • 效率:需要遍历样式表和关键帧,时间复杂度略高于场景1,但对于常规页面来说性能依然出色。

方案选择总结

  1. 总时长计算:优先使用document.getAnimations()结合时间范围计算的方案,兼顾准确性和效率。
  2. 结束值获取
    • 若动画设置fill: forwards,用场景1的异步获取计算样式方案,简单高效。
    • 若需要动画定义的原始结束值,用场景2的关键帧解析方案,准确性更高。

内容的提问来源于stack exchange,提问作者Temp Dragon

火山引擎 最新活动