countup.tsx 3.2 KB
Newer Older
1 2 3 4 5 6
import React, { CSSProperties, FunctionComponent, useEffect, useRef, useState } from 'react'
import './countup.scss'
import bem from '@/utils/bem'

export interface CountUpProps {
  maxLen: number
7
  endNumber: string
8 9 10 11 12 13 14 15
  delaySpeed?: number
  easeSpeed: number
  thousands: boolean
  className: string
  style: React.CSSProperties
}
const defaultProps = {
  maxLen: 0,
16
  endNumber: '',
17 18 19 20 21 22
  delaySpeed: 300,
  easeSpeed: 1,
  thousands: false,
  className: '',
} as CountUpProps
export const CountUp: FunctionComponent<Partial<CountUpProps>> = (props) => {
23
  const { maxLen, endNumber, delaySpeed, easeSpeed, className, thousands, ...reset } = {
24 25 26 27 28 29
    ...defaultProps,
    ...props,
  }
  const b = bem('countup')
  const countupRef = useRef<HTMLDivElement>(null)
  const timerRef = useRef(0)
30
  const numbers = Array.from({ length: 10 }, (v, i) => i)
31 32

  const getShowNumber = () => {
33
    const splitArr = endNumber.split('.')
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
    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('')
  }

  const numerArr = getShowNumber()

  const setNumberTransform = () => {
    if (countupRef.current) {
      const numberItems = countupRef.current.querySelectorAll('.nut-countup__number')
      const numberFilterArr: Array<string> = numerArr.filter((item: any) => !isNaN(item))
50 51
      for (let index in numberItems) {
        if (!Object.prototype.hasOwnProperty.call(numberItems, index)) continue
52 53 54
        const elem = numberItems[Number(index)] as HTMLElement
        const idx = Number(numberFilterArr[Number(index)])
        if ((idx || idx == 0) && elem) {
55
          // 计算规则:父元素和实际列表高度的百分比,分割成20等份
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
          const transform = `translate(0, -${(idx == 0 ? 10 : idx) * 5}%)`
          elem.style.transform = transform
          elem.style.webkitTransform = transform
        }
      }
    }
  }

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

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

  useEffect(() => {
    setNumberTransform()
  }, [numerArr])

  return (
    <div className={`${b()} ${className}`} ref={countupRef}>
      <ul className={b('list')}>
        {numerArr.map((item: any, idx: number) => {
          return (
            <li className={`${b('listitem', { number: !isNaN(item) ? true : false })}`} key={idx}>
              {!isNaN(item) ? (
                <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'