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

React实现时长一致的平滑数字递增动画方案咨询

固定时长的数字递增动画实现方案(React)

一、纯React + requestAnimationFrame 方案(推荐,无额外依赖)

之前用setInterval时长不准的核心问题是:它的执行间隔不精确,且靠固定步长控制进度,目标值大小会直接影响总时长。改用requestAnimationFrame能和浏览器刷新同步,基于时间占比计算当前数值,确保不管目标值是1还是5000,动画时长完全一致。

实现代码

import { useEffect, useState, useRef } from 'react';

const NumberCounter = ({ target = 0, duration = 2000, start = 0 }) => {
  const [currentNum, setCurrentNum] = useState(start);
  const animationRef = useRef(null);
  const startTimeRef = useRef(null);

  useEffect(() => {
    const startAnimation = () => {
      startTimeRef.current = performance.now();
      const updateNum = (currentTime) => {
        const elapsed = currentTime - startTimeRef.current;
        // 计算已过时间占总时长的比例,超过1则取1
        const progress = Math.min(elapsed / duration, 1);
        // 计算当前数值,四舍五入取整
        const calculatedNum = Math.floor(start + (target - start) * progress);
        setCurrentNum(calculatedNum);

        if (progress < 1) {
          animationRef.current = requestAnimationFrame(updateNum);
        }
      };
      animationRef.current = requestAnimationFrame(updateNum);
    };

    startAnimation();

    return () => {
      if (animationRef.current) {
        cancelAnimationFrame(animationRef.current);
      }
    };
  }, [target, duration, start]);

  return <span>{currentNum}</span>;
};

// 使用示例
// <NumberCounter target={5000} duration={2000} />

关键逻辑

  • 用performance.now()获取高精度时间,计算已流逝时间占总时长的比例
  • 基于比例计算当前应显示的数值,避免固定步长带来的时长偏差
  • 动画结束或组件卸载时,用cancelAnimationFrame清理定时器

二、SCSS + Data属性 结合React的方案

如果想借助CSS控制动画节奏,可通过CSS自定义属性+动画进度监听实现,核心是用CSS动画控制进度百分比,再由JS读取进度计算数值:

1. SCSS样式

.number-counter {
  --progress: 0%;
  animation: count-progress 2s linear forwards;
}

@keyframes count-progress {
  from {
    --progress: 0%;
  }
  to {
    --progress: 100%;
  }
}

2. React组件实现

import { useEffect, useState, useRef } from 'react';

const NumberCounter = ({ target = 0, duration = 2000, start = 0 }) => {
  const [currentNum, setCurrentNum] = useState(start);
  const counterRef = useRef(null);
  const animationFrameRef = useRef(null);

  useEffect(() => {
    const counterEl = counterRef.current;
    if (!counterEl) return;

    const updateNum = () => {
      // 读取CSS中--progress的当前值
      const progressStr = getComputedStyle(counterEl).getPropertyValue('--progress');
      const progress = parseFloat(progressStr) / 100;
      const calculatedNum = Math.floor(start + (target - start) * progress);
      setCurrentNum(calculatedNum);

      // 动画未结束时继续监听
      if (progress < 1) {
        animationFrameRef.current = requestAnimationFrame(updateNum);
      }
    };

    // 监听动画开始,启动数值更新
    const handleAnimationStart = () => {
      animationFrameRef.current = requestAnimationFrame(updateNum);
    };

    // 监听动画结束,设置最终值
    const handleAnimationEnd = () => {
      setCurrentNum(target);
      cancelAnimationFrame(animationFrameRef.current);
    };

    counterEl.addEventListener('animationstart', handleAnimationStart);
    counterEl.addEventListener('animationend', handleAnimationEnd);

    return () => {
      counterEl.removeEventListener('animationstart', handleAnimationStart);
      counterEl.removeEventListener('animationend', handleAnimationEnd);
      cancelAnimationFrame(animationFrameRef.current);
    };
  }, [target, duration, start]);

  return (
    <span 
      ref={counterRef}
      className="number-counter"
      // 可选:通过data属性传递目标值,方便后续扩展
      data-target={target}
    >
      {currentNum}
    </span>
  );
};

关键逻辑

  • CSS动画负责控制--progress变量从0%到100%,时长固定
  • React通过getComputedStyle读取实时进度,计算对应数值
  • 用animationstart和animationend事件配合requestAnimationFrame,保证数值更新和动画同步

内容的提问来源于stack exchange,提问作者Bobby Rösehag

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 17:30:48