numberAnimate icon indicating copy to clipboard operation
numberAnimate copied to clipboard

React 版本

Open liu-jin-yi opened this issue 5 months ago • 1 comments

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}
 />

liu-jin-yi avatar Aug 26 '24 07:08 liu-jin-yi