仿拼多多领红包、金额数字滚动如何实现?

拼多多现金大转盘领取红包后,小数部分有一个数字移动的效果,这个效果怎么做呢?

本文我会告诉你数字移动的原理,并用 React 实现一个 Demo,效果如下:

滚动原理

不难想到,数字移动,实质是一串数字列表,在容器内部向上移动。下图就展示了数字从 0 变为 2,又从 2 变为 4 的过程:

但是,金额数字比较特殊,它是可以进位的。举例来说,39.5 变为 39.9 时,小数部分由 5 到 9 需要向上移动;39.9 变为 40.2 时,小数部分由 9 变到 2 时也需要向上移动。

为了做到这个效果,我们需要每次滚动结束之后,重新设置一整串数字。

同样是从 0 变为 2,又从 2 变为 4。下图不同的是,数字变为 2 时,它下方的数字变为了 3、4、5、6、7、8、9、0、1;数字变为 4 时,它下方的数字变为了 5、6、7、8、9、0、1、2、3。

关键布局

了解原理后,我们开始写元素布局。关键布局有 2 个元素:

  • 选择框,它可以确认下一个将要变成的数字,我们用它来模拟领取红包之后、金额变化的情况。
  • 数字盒子,它包括三部分,带 overflow: hidden 的外层盒子,包裹数字并向上滚动的内层盒子,以及一个个数字。

点击查看代码

 

jsx

代码解读

复制代码

const App = function () { const [options] = useState([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) const [nums, setNums] = useState([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) return ( <main> <div className="select-box"> <span>改变数字:</span> <select> { options.map(v => ( <option key={v}>{v}</option> )) } </select> </div> <div className="num-box"> <div> { nums.map(v => ( <div className="num" key={v}>{v}</div> )) } </div> </div> </main> ) };

关键逻辑

数字移动的关键逻辑有 3 个:

  • 重置数字数组
  • 计算移动距离
  • 开启关闭动画

重置数字数组

你之前已经知道,如果数组首位是 2,它下面的数字就是 3、4、5、6、7、8、9、0、1。要获取完整的数组,我们可以分为两个步骤:

  • 首先,数组首位背后的数字依次加 1,这样数字就变为了 3、4、5、6、7、8、9、10、11;
  • 然后,所有大于 9 的数字都减去 10,这样数字就变为了 3、4、5、6、7、8、9、0、1。

点击查看代码

 

js

代码解读

复制代码

const getNewNums = (next) => { const newNums = [] for (let i = next; i < next + 10; i++) { const item = i > 9 ? (i - 10) : i newNums.push(item) } return newNums }

计算移动距离

你可以用 current 表示当前的数字,next 表示需要变成的数字。计算移动距离时,需要分两种情况考虑:

  • next 大于 current 时,只需要移动 next - current 个数字即可;
  • next 小于 current 时,需要先移动 10 - next 个数字,再移动 current 个数字即可。

点击查看代码

 

js

代码解读

复制代码

const calculateDistance = (current, next) => { const height = 40 let diff = next - current if (next < current) { diff = 10 - current + next } return -(diff * height) }

开启关闭动画

不难想到,我们数字移动的动画是使用 translateY 和 transition 实现。当数字移动时,我们把 translateY 设置为 calculateDistance 的结果;当移动结束、重置数组时,我们需要把 translateY 设置为 0。

整个过程中,如果我们一直开启动画,效果会是数字先向上移动,再向下移动,这并不符合预期。

因此,我们需要在数字开始移动时开启动画,数字结束移动后、重置数组前关闭动画。

具体代码

html:

<div id="app"></div>

css:

main {
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.select-box {
  display: flex;
  margin-bottom: 10px;
}

.num-box {
  width: 40px;
  height: 40px;
  overflow: hidden;
}

.num {
  width: 40px;
  height: 40px;
  line-height: 40px;
  text-align: center;
  font-size: 36px;
}

js:

import React, { useState, useRef } from 'react';
import ReactDom from 'react-dom';

const App = function () {
  const [nums, setNums] = useState([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
  const [nextNum, setNextNum] = useState(0)
  const [options] = useState([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
  const numBoxRef = useRef()
  const [numBoxY, setNumBoxY] = useState(0)

  const getNewNums = (next) => {
    const newNums = []
    for (let i = next; i < next + 10; i++) {
      const item = i > 9 ? (i - 10) : i
      newNums.push(item)
    }
    return newNums
  }

  const calculateDistance = (current, next) => {
    const height = 40
    let diff = next - current
    if (next < current) {
      diff = 10 - current + next
    }
    return -(diff * height)
  }

  const onChange = (e) => {
    const current = nums[0]
    const next = Number(e.target.value)
    const dis = calculateDistance(current, next)
    numBoxRef.current.style.transition = `all 1s`
    setNextNum(next)
    setNumBoxY(dis)
  }

  const onTransitionEnd = () => {
    const newNums = getNewNums(nextNum)
    numBoxRef.current.style.transition = ''
    setNums(newNums)
    setNumBoxY(0)
  }

  return (
    <main>
      <div className="select-box">
        <span>改变数字:</span>
        <select onChange={onChange}>
          {
            options.map(v => (
              <option key={v}>{v}</option>
            ))
          }
        </select>
      </div>
      <div className="num-box">
        <div
          ref={numBoxRef}
          style={{transform: `translateY(${numBoxY}px)`}}
          onTransitionEnd={onTransitionEnd}
        >
          {
            nums.map(v => (
              <div className="num" key={v}>{v}</div>
            ))
          }
        </div>
      </div>
    </main>
  )
};

ReactDom.render(<App />, document.getElementById('app'));
      

总结

本文介绍了类似拼多多的金额数字滚动如何实现,其中有三个关键点,分别是重置数字数组、计算移动距离和开启关闭动画。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

烟火漫天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值