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

React如何实现3个数字组件2秒递增动画同时结束(无第三方库)

数字同步递增动画实现方案

需求回顾

  • 3个接收不同目标数值的组件,数字递增动画总时长固定为2秒
  • 所有组件的动画需在同一时间点结束
  • 实现过程不引入第三方库

原有代码的问题

  • 采用「固定步长+1 + setInterval定时触发」的计数逻辑,不同目标值的总执行步数差异极大:目标值100的组件执行100次就结束,目标值700的组件需要执行700次,完全无法保证同时结束
  • setInterval传入0.01ms延迟属于无效参数,浏览器遵循W3C规范,嵌套定时器的最小延迟为4ms,小于该值的参数会被强制修正,根本达不到预期执行频率
  • 计数逻辑和真实时间完全脱钩,受浏览器事件循环调度、标签页后台节流、主线程长任务阻塞影响,实际运行时长偏差极大,无法稳定保证2秒的固定时长

核心实现要点

  • 动画逻辑必须以时间进度为核心计算依据,而非固定计数步长:记录动画启动的起始时间戳,每帧计算当前已消耗时长,通过「已消耗时长/总动画时长」得到动画完成百分比,再乘以目标值得到当前帧应该显示的数字
  • 用requestAnimationFrame做帧调度替代setInterval:它和浏览器屏幕刷新率同步,执行时机更稳定,动画更流畅,不会出现定时器排队导致的跳帧、卡顿问题
  • 时间计算优先使用performance.now()获取高精度时间戳,相比Date.now()不受系统时间修改影响,进度计算更准确
  • 必须做进度边界兜底:当计算出的动画进度超过100%时,直接将显示值设为最终目标值,终止帧调度,避免数值超出目标、动画超时不结束的问题
  • 组件卸载时要清理未执行完的动画帧任务,避免内存泄漏
  • 所有组件统一使用固定2秒的时长参数,只要组件在同一时间点挂载启动动画,自然会同步结束,不需要额外引入全局状态做同步控制

修正后的可运行代码

const {useState, useEffect, useRef} = React;

const AnimationNumber = ({ number, duration = 2000 }) => {
  const [displayNum, setDisplayNum] = useState(0);
  const animationTaskRef = useRef(null);

  useEffect(() => {
    // 记录动画启动的高精度起始时间
    const startTime = performance.now();

    const frameUpdate = () => {
      const elapsedTime = performance.now() - startTime;
      // 计算当前进度,最大不超过100%
      const progress = Math.min(elapsedTime / duration, 1);
      // 计算当前显示值,取整避免出现小数
      const currentValue = Math.floor(progress * number);
      setDisplayNum(currentValue);

      if (progress < 1) {
        animationTaskRef.current = requestAnimationFrame(frameUpdate);
      }
    };

    // 启动第一帧动画
    animationTaskRef.current = requestAnimationFrame(frameUpdate);

    // 组件卸载时清理动画任务
    return () => {
      if (animationTaskRef.current) {
        cancelAnimationFrame(animationTaskRef.current);
      }
    };
  }, [number, duration]);

  return (
    <div data-target={number}>
      <strong>
        <span>{displayNum}</span>
      </strong>
    </div>
  );
};

function Example() {
  return (
    <div>
      <AnimationNumber number={700} />
      <AnimationNumber number={100} />
      <AnimationNumber number={470} />
    </div>
  );
}

ReactDOM.render(<Example />, document.getElementById('root'));

内容的提问来源于stack exchange,提问作者horoyoi o

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 01:51:43