// @ts-nocheck import React, { Component } from 'react' import classNames from 'classnames' import { filterForNum, formatNum, getNumLength, getRawArray } from './utils' import _ from 'lodash' interface FlipNumberProps { /* 最后显示的数字 */ to: number /* 起始数字,默认为 0 */ from?: number /* 延迟,默认为 0 */ delay?: number /* 滚动时长,默认为 1500 毫秒 */ duration?: number /** * 滚动函数,控制滚动的加速度,默认起末慢,中间快 * @see https://github.com/danro/easing-js/blob/4f5e7edbde7f7200a1baf08e357377896c0d207e/easing.js#L39-L42 */ easeFn?(percent: number): number /* 是否逐个数字滚动,默认 true */ individually?: boolean /* 小数点个数,默认无小数点 */ decimal?: number /* 是否启用逗号分隔数字 */ useGroup?: boolean className?: string } interface FlipNumberState { height: number heightList: number[] } class FlipNumber extends Component { static defaultProps = { from: 0, duration: 1500, individually: true, decimal: 0, easeFn(percent: number): number { return (percent /= 0.5) < 1 ? 0.5 * Math.pow(percent, 3) : 0.5 * (Math.pow(percent - 2, 3) + 2) }, } constructor(props) { super(props) // eslint-disable-next-line react/state-in-constructor this.state = { height: 0, heightList: [], } this.height = 0 this.numberArray = [...Array(10).keys()] this.doInitData(props) } componentDidMount() { this.height = this['gm-flip-number-digit0'].clientHeight / (this.numberArray.length + 1) this.doInitView(this.props) } // eslint-disable-next-line camelcase UNSAFE_componentWillReceiveProps(nextProps) { if (nextProps.to !== this.props.to) { window.cancelAnimationFrame(this.requestId) clearTimeout(this.timeoutID) this.doInitData(nextProps) this.doInitView(nextProps) } } doInitData = (props) => { const { from, to, decimal, useGroup } = props // 小数点 + useGroup this.fromStr = formatNum(from, decimal, useGroup) this.toStr = formatNum(to, decimal, useGroup) // 格式化滚动数字数组 const totalLen = getNumLength(this.fromStr, this.toStr) this.toRawArr = getRawArray(this.toStr, totalLen) // to 的字符串数组 this.toNumArr = filterForNum(this.toRawArr.rawList).map(Number) // to 的去掉',' '.'后的数字数组 this.digitLen = this.toNumArr.length this.fromRawArr = getRawArray(this.fromStr, totalLen) this.fromNumArr = _.map([...Array(this.digitLen)], (_) => 0) } doInitView = (props) => { const { delay, duration } = props const fromNum = parseInt(filterForNum(this.fromStr.split('')).join(''), 10) const heightList = [] _.forEach(this.toNumArr, (dom, index) => { const height = this.onDraw({ from: this.fromNumArr[index], percent: 1, alter: Math.floor(fromNum / Math.pow(10, index)), }) heightList.unshift(height) }) this.setState({ heightList: heightList, }) delay ? (this.timeoutID = setTimeout(() => this.flipTo(duration), delay)) : this.flipTo(duration) } /** * @description 数字的绘制函数,通过上下移动每一条数字轴来实现 * @argument from 开始滚动的数字 * @argument percent 已经滚动的时间占总滚动时间 duration 的比例 * @argument alter from 变成 to 每个数字轴需要改变的距离 * @returns 返回数字轴应该移动的距离 */ onDraw = ({ from, percent, alter }) => { const expectNum = (percent * alter + from) % 10 // 表示需要滚动多少个数字,小数居多,整数表示刚好显示完全一个数字 return -expectNum * this.height } flipTo = (duration) => { const { easeFn, individually } = this.props this.fromNumArr = filterForNum(this.fromRawArr.rawList).map(Number) const draw = (percent) => { let temp = 0 const heightList = [] for (let d = this.toNumArr.length - 1; d >= 0; d--) { const alter = this.toNumArr[d] - this.fromNumArr[d] temp += alter const height = this.onDraw({ from: this.fromNumArr[d], percent: easeFn(percent), alter: individually ? temp : alter, }) heightList.push(height) temp *= 10 } this.setState({ heightList, }) } const startTime = window.performance.now() const tick = (now) => { const timeConsuming = now - startTime draw(timeConsuming / duration) if (timeConsuming < duration) this.requestId = window.requestAnimationFrame(tick) else { draw(1) } } this.requestId = window.requestAnimationFrame(tick) } renderDigitAxis = () => { const { heightList } = this.state const digitAxis = _.map(this.toNumArr, (item, index) => (
{ this[`gm-flip-number-digit${index}`] = rel }} className='gm-inline-block gm-position-relative' key={`digitAxis${index}`} > {_.map(this.numberArray, (i, d) => (
{i}
))}
{this.numberArray[0]}
)) _.forEach(this.toRawArr.symbolList, (item, index) => { const symbolAxis = (
{_.map(this.numberArray, (i, d) => (
{item.symbol}
))}
{item.symbol}
) digitAxis.splice(item.position, 0, symbolAxis) }) return digitAxis } render() { const { className, to } = this.props return (
{ this.wrap = rel }} style={{ height: `${this.height}px` }} className={classNames('gm-position-relative gm-overflow-hidden', className)} > {this.renderDigitAxis()}
) } } export default FlipNumber export type { FlipNumberProps }