抽奖动画 - 十二宫格抽奖

本文介绍一种12宫格抽奖,这种抽奖方式是九宫格抽奖的演变,只不过开始播放的时候是随机方式,不是顺时针播放了。

1.需求

先看看高保,如下图1
image
图1

  1. 用户点击"立即抽奖"后12个奖品随机高亮
  2. 请求接口,待所有产品都高亮后,根据接口结果,固定在抽中奖品上
  3. 最后弹出中奖结果弹框

2.思路

2.1 高亮

实现图片的高亮有很多种方式,上一篇九宫格抽奖中使用的是修改图片容器的背景色,这里也可以让UI同事设计两套图片,一套是普通显示的图片,一套是高亮显示的,这样只要使用vue动态属性来切换就可以了。

2.2 随机闪烁

随机闪烁需要用到随机数组,可以先得到奖品数据的下标数组,然后随机打乱这个数组得到混乱的下标,最后遍历数组查找对应的下标,切换高亮属性。

2.3 定时器

最后,要看到闪烁的结果,不能直接遍历数组,需要一个定时器,我们这里依然使用定时任务+async的方式实现,具体方式在下面实现过程步骤中详细介绍。

3.实现过程

3.1 奖品数据

按照高保给的顺序定义奖品数组,注意数组设置一个active属性,表示当前奖品是否高亮。代码如下:

prizes: [
    {active: false, prizeName: '华为智能手环4', imgName: '1-huawei'},
    {active: false, prizeName: '米家声波电动牙刷T300', imgName: '1-mijia'},
    {active: false, prizeName: 'AX1800千兆双频路由器', imgName: '1-router'},
    {active: false, prizeName: 'vivo TWS Neo耳机', imgName: '1-100m'},
    {active: false, prizeName: 'OPPO w31真无线耳机', imgName: '1-oppo_w31'},
    {active: false, prizeName: 'vivo Watch手表', imgName: '1-200m'},
    {active: false, prizeName: 'reno4智能手机', imgName: '1-10y'},
    {active: false, prizeName: '华为智能手表', imgName: '1-1g'},
    {active: false, prizeName: 'vivo X5 Pro', imgName: '1-20g'},
    {active: false, prizeName: 'vivo TWS Neo耳机', imgName: '1-vivo_neo'},
    {active: false, prizeName: '小米10智能手机', imgName: '1-100y'},
    {active: false, prizeName: 'vivo X50 Pro', imgName: '1-2g'}
]

3.2 高亮

这里UI提供了12个奖品图片对应的高亮图片,只要切换成高亮的图片就可以了,这个使用vue动态属性很容易实现。图片风格由UI同事负责设计,在这里要区分当图片的选中状态,我们在原图片名字后面加上后缀“_on”来区分选中图片,如下图2
image
图2

3.3 随机数组

有了上面的奖品数据,可以很容得到下标数组,然后要打断这个数组,得到一个随机数字数组。这里使用了洗牌算法来打乱数组,方法如下:

//随机排列数,洗牌算法
shuffle(arr) {
    let i = arr.length, t, j
    while (i) {
        j = Math.floor(Math.random() * (i--))
        t = arr[i]
        arr[i] = arr[j]
        arr[j] = t
    }
}

调用方式如下:

let arr = Array(this.prizes.length).fill(0).map((i, index) => index)
console.log("打乱前的数组:", arr)
//随机打乱数组元素
this.shuffle(arr)
console.log("打乱后的数组", arr)

结果如下图3
image
图3

3.4 定时器&异步事件

这里使用一个定时器来执行延迟,定时器在一个延迟时间结束后再执行一个事件,这样这个事件就是一个异步事件了。

然后使用async/await把异步任务串起来,使它们按照一定的顺序来执行。代码如下:

let arr = Array(this.prizes.length).fill(0).map((i, index) => index)
let timeoutID = null
//模拟定时器
let sleep = time => new Promise((resolve) => {
    timeoutID = setTimeout(resolve, time)
})
//定时任务函数,async/await
let collectEvent = async(count, interval) => {
    for (let i = 0; i < count; i++) {
        //等待上一个任务完成
        await sleep(interval)
        //按照随机数显示高亮
        this.prizes.forEach((p, index) => { p.active = index === arr[i]  })
    }
}
//产生随机数组
this.shuffle(arr)
//加入定时任务
collectEvent(arr.length, 300).then(() => {
    clearTimeout(timeoutID)
})

加入定时任务的时候,collectEven方法内有12个await语句,每个await后面又是一个异步任务,这样执行collectEvent的时候会等待上一个await后面的语句有了返回再执行下一个await语句。sleep()方法会再规定时间(参数interval,传值300)内执行resolve,这样每次高亮都会间隔300毫秒时间,看上去是在随机高亮。效果如下图4
image
图4
这里是随机闪烁,如果要求按照一定的顺序来闪烁,就需要按照顺序构造数组arr了。

3.5 时间间隔

这里闪烁的间隔是300毫秒,如果需要由快到慢或者由慢到快的闪烁,就需要在sleep()上做些改动,例如我们可以给sleep()函数传值的时候用一个表达式,使用Math.pow()来得到时间间隔,将调用sleep的语句修改一下

await sleep(interval)

改成

await sleep(interval * i)

然后调用的时候修改一下时间

collectEvent(arr.length, 300)

改成

collectEvent(arr.length, 100)

这样每次执行异步任务的间隔时间会比上一次多100毫秒,来看下效果如下图5
image
图5

3.6 中奖弹框

最后执行完闪烁之后要根据接口返回来固定到某个奖品上并弹出中奖弹框。使用一个随机选择函数模拟抽奖接口,函数如下:

//生成随机奖品
getRandomIntInclusive(min, max) {
    min = Math.ceil(min)
    max = Math.floor(max)
    return Math.floor(Math.random() * (max - min + 1)) + min
}

调用方式如下:

//加入定时任务
collectEvent(arr.length, 100).then(() => {
    clearTimeout(timeoutID)
    let checkedPrizeIndex = this.getRandomIntInclusive(0, this.prizes.length - 1)
    this.prizes.forEach((p, index) => { p.active = index === checkedPrizeIndex })
    this.prizeInfo = this.prizes[checkedPrizeIndex]
    this.raffleResultShow = true
})

如何实现中奖弹框就不再赘述了,最后的效果如下图6
image
图6

4.总结

本文介绍了如何实现12宫格抽奖功能,主要用到的技术有随机打乱数组(洗牌算法),定时任务,异步任务队列等。

posted @ 2022-08-12 14:15  nd  阅读(413)  评论(0编辑  收藏  举报