numberAnimate
numberAnimate copied to clipboard
React 版本
React 版本 TS + tailwindcss
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
type NumberAnimateProps = {
speed?: number;
num: string | number;
iniAnimate?: boolean;
symbol?: string;
dot?: number;
pst?: boolean;
charWidth: number;
displayHeight: number;
};
// line-height 需要在外层设置。 一定, 字体大小继承
export const NumberAnimate: React.FC<NumberAnimateProps> = ({
speed = 1000,
num = "",
iniAnimate = true,
symbol = "",
dot = 0,
pst = false,
charWidth,
displayHeight,
}) => {
const [numArray, setNumArray] = useState<string[]>([]);
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setNumArray(parseNumber(num, dot));
}, [num, dot]);
useEffect(() => {
if (containerRef.current && iniAnimate) {
executeAnimation(containerRef.current);
}
}, [numArray, iniAnimate]);
const parseNumber = useCallback((num: string | number, dot: number) => {
return parseFloat(num.toString()).toFixed(dot).split("");
}, []);
const executeAnimation = useCallback(
(parent: HTMLElement) => {
Array.from(parent.querySelectorAll(".mt-number-animate-dom")).forEach(
(dom: Element) => {
const num = (dom as HTMLElement).dataset.num!;
const spanHeight = (dom as HTMLElement).offsetHeight / 12;
const targetTop = -+num * spanHeight + "px";
(dom as HTMLElement).style.transition = `transform ${speed / 1000}s`;
(dom as HTMLElement).style.transform = `translateY(${targetTop})`;
}
);
},
[speed]
);
const nums = useMemo(
() =>
[...Array(10).keys(), ".", 0].map((num, spanIndex) => (
<span key={spanIndex} className="w-full float-left">
{num}
</span>
)),
[]
);
return (
<div ref={containerRef}>
<div
className="overflow-hidden inline-block relative"
style={{ height: displayHeight }}
>
{numArray.map((char, index) => (
<React.Fragment key={index}>
{index !== 0 &&
(numArray.length - index) % 3 === 0 &&
char !== "." &&
symbol && (
<div
className="float-left text-center"
style={{ width: charWidth }}
>
{symbol}
</div>
)}
<div
className="mt-number-animate-dom text-center float-left relative top-0"
style={{ width: charWidth }}
data-num={char}
>
{nums}
</div>
</React.Fragment>
))}
{pst && <span>%</span>}
</div>
</div>
);
};
使用示例:
<NumberAnimate
displayHeight={15}
charWidth={8}
num={10}
/>