React实现滚动至视口时启动的单次递增计数器
React 实现视口触发的单次数字滚动计数效果
下面是一个通用的实现方案,满足元素进入视口启动计数、到指定值停止、仅执行一次的需求,代码可直接复用或按需调整:
import { useEffect, useRef, useState } from 'react'; const CountUpOnVisible = ({ targetNumber, duration = 2000, className = '' }) => { const countRef = useRef(null); const hasCountedRef = useRef(false); const [currentCount, setCurrentCount] = useState(0); useEffect(() => { const element = countRef.current; if (!element || hasCountedRef.current) return; const checkVisibilityAndCount = () => { const rect = element.getBoundingClientRect(); // 判断元素是否进入视口(顶部小于窗口高度,底部大于0) const isVisible = rect.top < window.innerHeight && rect.bottom > 0; if (isVisible && !hasCountedRef.current) { hasCountedRef.current = true; let start = 0; const increment = targetNumber / (duration / 16); // 按16ms一帧计算增量 const updateCount = () => { start += increment; if (start >= targetNumber) { setCurrentCount(targetNumber); return; } setCurrentCount(Math.floor(start)); requestAnimationFrame(updateCount); }; requestAnimationFrame(updateCount); // 计数开始后移除监听,避免重复触发 window.removeEventListener('scroll', checkVisibilityAndCount); window.removeEventListener('resize', checkVisibilityAndCount); } }; // 初始检查一次(元素可能一开始就在视口) checkVisibilityAndCount(); // 添加滚动和窗口大小变化监听 window.addEventListener('scroll', checkVisibilityAndCount); window.addEventListener('resize', checkVisibilityAndCount); // 清理函数 return () => { window.removeEventListener('scroll', checkVisibilityAndCount); window.removeEventListener('resize', checkVisibilityAndCount); }; }, [targetNumber, duration]); return ( <span ref={countRef} className={className}> {currentCount} </span> ); }; // 使用示例 const App = () => { return ( <div style={{ height: '150vh', display: 'flex', justifyContent: 'center', alignItems: 'center' }}> <div> 累计用户数:<CountUpOnVisible targetNumber={12345} duration={2500} /> </div> </div> ); }; export default App;
关键细节说明
- 视口判断逻辑:通过
getBoundingClientRect()获取元素的位置信息,结合window.innerHeight判断元素是否进入视口范围,确保只有元素可见时才启动计数。 - 单次执行控制:用
hasCountedRef标记是否已完成计数,初始为false,计数启动后设为true,后续不再触发逻辑;同时计数开始后立即移除滚动/resize监听,进一步避免重复执行。 - 平滑计数动画:使用
requestAnimationFrame实现流畅的帧动画,根据传入的duration计算每帧的增量,确保计数过程均匀平滑。 - 通用性:组件通过props接收目标数值、动画时长、样式类名,可直接在任意需要的地方复用,无需针对特定场景修改核心逻辑。
- 初始检查:挂载时先执行一次可见性检查,避免元素一开始就在视口但未触发计数的情况。
内容的提问来源于stack exchange,提问作者Duka93
相关产品推荐
相关产品推荐

