如何在ReactJS中不使用外部包实现里程表样式过渡效果?
纯React实现无依赖里程表(Odometer)效果
基础计数动画版本
这个版本实现数字从初始值平滑过渡到目标值的效果,核心用React内置hooks结合requestAnimationFrame实现流畅动画:
import { useState, useEffect, useRef } from 'react'; const Odometer = ({ targetValue, duration = 2000 }) => { const [currentValue, setCurrentValue] = useState(0); const startValueRef = useRef(0); const startTimeRef = useRef(null); useEffect(() => { startValueRef.current = currentValue; startTimeRef.current = Date.now(); const updateValue = () => { const elapsed = Date.now() - startTimeRef.current; const progress = Math.min(elapsed / duration, 1); // 用easeOutQuad缓动函数让动画更自然 const easedProgress = 1 - Math.pow(1 - progress, 2); const newValue = Math.floor(startValueRef.current + (targetValue - startValueRef.current) * easedProgress); setCurrentValue(newValue); if (progress < 1) { requestAnimationFrame(updateValue); } }; requestAnimationFrame(updateValue); }, [targetValue]); return <div className="odometer-value">{currentValue}</div>; }; // 使用示例 const App = () => { return <Odometer targetValue={100} />; }; export default App;
进阶:逐位滚动的真实里程表效果
如果要模拟真实里程表的逐位数字滚动,可将数字拆分为单独数位,每个数位独立做滚动动画:
import { useState, useEffect, useRef } from 'react'; import './Odometer.css'; const Digit = ({ targetDigit, duration = 2000 }) => { const [currentDigit, setCurrentDigit] = useState(0); const startDigitRef = useRef(0); const startTimeRef = useRef(null); useEffect(() => { startDigitRef.current = currentDigit; startTimeRef.current = Date.now(); const updateDigit = () => { const elapsed = Date.now() - startTimeRef.current; const progress = Math.min(elapsed / duration, 1); const easedProgress = 1 - Math.pow(1 - progress, 2); // 处理跨0的滚动逻辑(比如9→0) let diff = targetDigit - startDigitRef.current; if (diff < 0) diff += 10; const newDigit = (startDigitRef.current + Math.floor(diff * easedProgress)) % 10; setCurrentDigit(newDigit); if (progress < 1) { requestAnimationFrame(updateDigit); } }; requestAnimationFrame(updateDigit); }, [targetDigit]); return <div className="odometer-digit">{currentDigit}</div>; }; const Odometer = ({ targetValue, duration = 2000 }) => { // 将目标值转换为补位后的数位数组,比如100转为[1,0,0] const digits = targetValue.toString().split('').map(Number); return ( <div className="odometer-value"> {digits.map((digit, index) => ( <Digit key={index} targetDigit={digit} duration={duration} /> ))} </div> ); }; // Odometer.css 样式示例 /* .odometer-value { display: flex; gap: 4px; font-family: monospace; font-size: 24px; } .odometer-digit { width: 24px; text-align: center; border: 1px solid #ddd; border-radius: 4px; padding: 4px; } */ // 使用示例 const App = () => { return <Odometer targetValue={100} />; }; export default App;
核心说明
- 用
requestAnimationFrame替代setInterval,避免掉帧,保证动画流畅度 - 加入缓动函数让数字过渡更自然,而非生硬的线性变化
- 进阶版本处理了数字跨0的滚动逻辑,更贴近真实里程表体验
- 所有实现仅依赖React内置hooks,无任何第三方包
内容的提问来源于stack exchange,提问作者RamkumarR
相关产品推荐
相关产品推荐

