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
相关产品推荐
相关产品推荐

