import BigNumber from 'bignumber.js'; import { useEffect, useMemo, useState } from 'react'; function getIsElementVisible(elem?: HTMLDivElement) { if (!elem || !elem.getBoundingClientRect) { return false; } const viewPortHeight = window.innerHeight; const { top: rectTop, bottom: rectBottom } = elem.getBoundingClientRect(); return rectTop > 0 && rectBottom < viewPortHeight; } /** * 自增长动画数字 * @param number 目标数字 * @param time? 动画时间,默认1000 * @param elem? 要显示位置的 ele。如果传入,则判断如果用户不能看到该 ele,则无动画效果,直接返回数字(节省性能开销) * @returns 动画数字 */ export function useAnimatedNumber({ number, time = 1000, elem, }: { number?: BigNumber | null; time?: number; elem?: HTMLDivElement; }) { const [animateNumber, setAnimateNumber] = useState(''); const visible = useMemo(() => { if (!number) return false; return getIsElementVisible(elem); }, [number, elem]); useEffect(() => { let intervalTime: NodeJS.Timer; function computed() { if (!number || number.isZero()) return; if (!visible) return; const millisecond = 20; const count = number.div(time / millisecond); intervalTime = setInterval(() => { setAnimateNumber((prev) => { const result = count.plus(prev || 0); if (result.gt(number)) { clearInterval(intervalTime); return number; } return result; }); }, millisecond); } computed(); // 设计不需要滚动到再显示动画效果,所以先去掉 // window.addEventListener('scroll', computed); return () => { // window.removeEventListener('scroll', computed); clearInterval(intervalTime); }; }, [number, time, elem, visible]); return visible ? animateNumber : number; }