import React from "react"; import { Spring, animated } from "react-spring"; import { useInView } from "react-intersection-observer"; const NUMBERS = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ]; // utils function getRandomIntInclusive(min, max) { min = Math.ceil(min); max = Math.floor(max); return Math.floor(Math.random() * (max - min + 1)) + min; } // lib const AnimatedNumber = ({ animateToNumber, fontStyle, configs, includeComma, }) => { const { ref, inView } = useInView({ triggerOnce: true }); const keyCount = React.useRef(0); const animteTonumberString = includeComma ? Math.abs(animateToNumber).toLocaleString() : String(Math.abs(animateToNumber)); const animateToNumbersArr = Array.from(animteTonumberString, Number).map( (x, idx) => (isNaN(x) ? animteTonumberString[idx] : x) ); const [numberHeight, setNumberHeight] = React.useState(0); const numberDivRef = React.useRef(null); const setConfig = (configs, number, index) => { if (typeof configs === "function") { return configs(number, index); } return configs ? configs[getRandomIntInclusive(0, configs.length - 1)] : undefined; }; React.useEffect(() => { const height = numberDivRef.current.getClientRects()?.[0]?.height; if (height) { setNumberHeight(height); } }, [animateToNumber, fontStyle]); return ( <> {numberHeight !== 0 && ( <div ref={ref} style={{ display: "flex", flexDirection: "row" }} className="animated-container" > {inView && animateToNumber < 0 && <div style={fontStyle}>-</div>} {inView && animateToNumbersArr.map((n, index) => { if (typeof n === "string") { return ( <div key={index} style={{ ...fontStyle }}> {n} </div> ); } return ( <div key={index} style={{ height: numberHeight, overflow: "hidden", }} > <Spring key={`${keyCount.current++}`} from={{ transform: "translateY(0px)", }} to={{ transform: `translateY(${ -1 * (numberHeight * animateToNumbersArr[index]) - numberHeight * 20 })`, }} config={setConfig(configs, n, index)} > {(props) => NUMBERS.map((number, i) => ( <animated.div key={i} style={{ ...fontStyle,...props }} > {number} </animated.div> )) } </Spring> </div> ); })} </div> )} <div ref={numberDivRef} style={{ position: "absolute", top: -9999, ...fontStyle }} > {0} </div> </> ); }; const Enhanced = React.memo(AnimatedNumber, (prevProps, nextProps) => { return prevProps.animateToNumber === nextProps.animateToNumber && prevProps.fontStyle === nextProps.fontStyle && prevProps.includeComma === nextProps.includeComma; }) export default Enhanced;