|
@@ -0,0 +1,69 @@
|
|
|
+import clsx from "clsx";
|
|
|
+import React from "react";
|
|
|
+import styles from "./index.module.scss";
|
|
|
+
|
|
|
+interface Props {
|
|
|
+ value: number;
|
|
|
+ numClassName?: string;
|
|
|
+ prefix?: string;
|
|
|
+}
|
|
|
+
|
|
|
+const NumberBox: React.FC<Props> = ({ value, numClassName, prefix }) => {
|
|
|
+ const [innerValue, setInnerValue] = React.useState<string[]>([]);
|
|
|
+ const [prev, setPrev] = React.useState<string[]>([]);
|
|
|
+ const [changeIdxs, setChangeIdx] = React.useState<number[]>([]);
|
|
|
+
|
|
|
+ React.useEffect(() => {
|
|
|
+ const toValue = `${value.toFixed(2)}`.split("");
|
|
|
+ setInnerValue(toValue);
|
|
|
+ }, [value]);
|
|
|
+
|
|
|
+ React.useEffect(() => {
|
|
|
+ if (prev?.length) {
|
|
|
+ const toChange: number[] = [];
|
|
|
+ innerValue.forEach((item, idx) => {
|
|
|
+ if (innerValue.length === prev.length) {
|
|
|
+ if (item !== prev[idx]) {
|
|
|
+ toChange.push(idx);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (innerValue.length > prev.length) {
|
|
|
+ const num = innerValue.length - prev.length;
|
|
|
+ if (idx < num) return;
|
|
|
+ if (item !== prev[idx - num]) {
|
|
|
+ toChange.push(idx);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ setChangeIdx(toChange);
|
|
|
+ setTimeout(() => {
|
|
|
+ setChangeIdx([]);
|
|
|
+ }, 1000);
|
|
|
+ }
|
|
|
+ setPrev(innerValue);
|
|
|
+ // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
+ }, [innerValue]);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className={styles.numberBox}>
|
|
|
+ <div className={clsx(styles.number, numClassName)}>{prefix}</div>
|
|
|
+ {innerValue.map((item, idx) => {
|
|
|
+ return (
|
|
|
+ <div
|
|
|
+ key={idx}
|
|
|
+ className={clsx(styles.number, numClassName)}
|
|
|
+ style={{
|
|
|
+ animation: changeIdxs.includes(idx)
|
|
|
+ ? `NumberScorll 0.${Number(item) < 3 ? 3 : item}s ease-in-out`
|
|
|
+ : "",
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {item}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+export default NumberBox;
|