countup.tsx 3.3 KB
Newer Older
O
oasis-cloud 已提交
1 2 3 4
import React, {
  CSSProperties,
  FunctionComponent,
  useEffect,
5
  useMemo,
O
oasis-cloud 已提交
6 7
  useRef,
} from 'react'
O
oasis-cloud 已提交
8

9 10 11 12
import bem from '@/utils/bem'

export interface CountUpProps {
  maxLen: number
13
  endNumber: string
14 15 16 17 18 19 20 21
  delaySpeed?: number
  easeSpeed: number
  thousands: boolean
  className: string
  style: React.CSSProperties
}
const defaultProps = {
  maxLen: 0,
22
  endNumber: '',
23 24 25 26 27 28
  delaySpeed: 300,
  easeSpeed: 1,
  thousands: false,
  className: '',
} as CountUpProps
export const CountUp: FunctionComponent<Partial<CountUpProps>> = (props) => {
O
oasis-cloud 已提交
29 30 31 32 33 34 35 36 37
  const {
    maxLen,
    endNumber,
    delaySpeed,
    easeSpeed,
    className,
    thousands,
    ...reset
  } = {
38 39 40 41 42 43
    ...defaultProps,
    ...props,
  }
  const b = bem('countup')
  const countupRef = useRef<HTMLDivElement>(null)
  const timerRef = useRef(0)
44
  const numbers = Array.from({ length: 10 }, (v, i) => i)
45 46

  const getShowNumber = () => {
47
    const splitArr = endNumber.split('.')
48 49 50 51 52 53 54 55 56 57
    const intNumber =
      maxLen && splitArr[0].length < maxLen
        ? (Array(maxLen).join('0') + splitArr[0]).slice(-maxLen)
        : splitArr[0]
    const currNumber = `${
      thousands ? intNumber.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,') : intNumber
    }${splitArr[1] ? '.' : ''}${splitArr[1] || ''}`
    return currNumber.split('')
  }

58
  const numerArr = useMemo(getShowNumber, [endNumber, maxLen, thousands])
59 60 61

  const setNumberTransform = () => {
    if (countupRef.current) {
O
oasis-cloud 已提交
62 63 64 65
      const numberItems = countupRef.current.querySelectorAll(
        '.nut-countup__number'
      )
      const numberFilterArr: Array<string> = numerArr.filter(
66
        (item: string) => !Number.isNaN(Number(item))
O
oasis-cloud 已提交
67
      )
68 69 70 71
      Object.keys(numberItems).forEach((key) => {
        const elem = numberItems[Number(key)] as HTMLElement
        const idx = Number(numberFilterArr[Number(key)])
        if ((idx || idx === 0) && elem) {
72
          // 计算规则:父元素和实际列表高度的百分比,分割成20等份
73
          const transform = `translate(0, -${(idx === 0 ? 10 : idx) * 5}%)`
74 75 76
          elem.style.transform = transform
          elem.style.webkitTransform = transform
        }
77
      })
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
    }
  }

  const numberEaseStyle: CSSProperties = {
    transition: `transform ${easeSpeed}s ease-in-out`,
  }

  useEffect(() => {
    timerRef.current = window.setTimeout(() => {
      setNumberTransform()
    }, delaySpeed)
    return () => {
      window.clearTimeout(timerRef.current)
    }
  }, [numerArr])

  return (
    <div className={`${b()} ${className}`} ref={countupRef}>
      <ul className={b('list')}>
97
        {numerArr.map((item: string, idx: number) => {
98
          return (
O
oasis-cloud 已提交
99
            <li
100 101 102
              className={`${b('listitem', {
                number: !Number.isNaN(Number(item)),
              })}`}
O
oasis-cloud 已提交
103 104
              key={idx}
            >
105
              {!Number.isNaN(Number(item)) ? (
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
                <span className={b('number')} style={numberEaseStyle}>
                  {[...numbers, ...numbers].map((number, subidx) => {
                    return <span key={subidx}>{number}</span>
                  })}
                </span>
              ) : (
                <span className={b('separator')}>{item}</span>
              )}
            </li>
          )
        })}
      </ul>
    </div>
  )
}

CountUp.defaultProps = defaultProps
CountUp.displayName = 'NutCountUp'