抽奖动画 - 九宫格抽奖

时间:2024-01-30 21:26:48

本文介绍九宫格抽奖功能的实现。

1.需求

功能很简单,来看看高保截图,如下图1
image
图1
需求的功能点如下:

  1. 用户点击抽奖,九宫格四周的图片顺时针依次闪烁,空转几圈。
  2. 请求接口,等接口有返回后最后对应的奖品闪烁,其他奖品不闪烁。
  3. 登录后,正中间的抽奖这个小方格点亮,未登录是灰色,这一点和抽奖无关,本文不做介绍。
  4. 最后弹框弹出抽奖结果。

2.整体思路

图片闪烁,只要图片所在的dom的背景色和其他不一样就可以了,如上图1,5元话费的背景是红色的。顺时针依次闪烁的话只要不断切换小方格的样式,当然要按照次序来,需要给小方格排个序,并且正中间这个“抽奖”按钮不能放在排序中。
关于依次闪烁,这里想到的是setTimeout方法,并且需要按照一定的顺序执行定时器,可以通过控制setTimeout(fn, timeout)的第二个参数来实现这个效果。

3.实现过程

3.1 布局

这里把数据放在一个数组中,包含奖品图片,奖品ID,奖品中奖后的图片等。为了布局方便,把中间的抽奖按钮和两个谢谢合作也放在数据中。代码如下:

luckyDrawList: [
    {
        id: 1,
        prizeId: "1008604",
        active: false,
        imgSrc: require('../../assets/images/blog/lucky1@3x.png'),
        prizeSrc: require("../../assets/images/blog/prize1@2x.png"),
        prizeName: "10元红包"
    },
    {
        id: 2,
        prizeId: "1008606",
        active: false,
        imgSrc: require('../../assets/images/blog/lucky2@3x.png'),
        prizeSrc: require("../../assets/images/blog/prize2@2x.png"),
        prizeName: "5元红包"
    },
    {
        id: 3,
        prizeId: "1008602",
        active: false,
        imgSrc: require('../../assets/images/blog/lucky3@3x.png'),
        prizeSrc: require("../../assets/images/blog/prize3@2x.png"),
        prizeName: "50元红包"
    },
    {
        id: 4,
        prizeId: "1008603",
        active: false,
        imgSrc: require('../../assets/images/blog/lucky4@3x.png'),
        prizeSrc: require("../../assets/images/blog/prize4@2x.png"),
        prizeName: "30元红包"
    },
    {
        id: 5,
        active: false,
        imgSrc: require('../../assets/images/blog/lucky5@3x.png'),
        disableImg: require('../../assets/images/blog/lucky52@2x.png')
    },
    {
        id: 6,
        active: false,
        imgSrc: require('../../assets/images/blog/lucky67@3x.png'),
        prizeName: "谢谢参与"
    },
    {
        id: 7,
        active: false,
        imgSrc: require('../../assets/images/blog/lucky67@3x.png'),
        prizeName: "谢谢参与"
    },
    {
        id: 8,
        prizeId: "1008601",
        active: false,
        imgSrc: require('../../assets/images/blog/lucky8@3x.png'),
        prizeSrc: require('../../assets/images/blog/prize8@2x.png'),
        prizeName: "100元京东超市红包"
    },
    {
        id: 9,
        prizeId: "1008605",
        active: false,
        imgSrc: require('../../assets/images/blog/lucky9@3x.png'),
        prizeSrc: require('../../assets/images/blog/prize9@2x.png'),
        prizeName: "一朵鲜花"
    }
]

九宫格布局可以采用流行的flex布局,给容器设置宽度,高度,然后设置display: flex;并且align-content: space-between;每个格子设置固定宽度高度,这样也不需要设置margin,自然分布在容器中,代码如下:

.lucky-draw-list {
    margin: 0 auto;
    width: 530px;
    height: 519px;
    @include flex(row, normal, normal, wrap, space-between);
    .lucky-draw-item {
        position: relative;
        width: 176px;
        height: 171px;
        border-radius: 20px;
        color: #fff;
        vertical-align: bottom;
        img {
            width: 160px;
            height: 157px;
        }
        .start-tips-times {
            position: absolute;
            left: 50%;
            transform: translateX(-50%);
            bottom: 40px;
            width: 100%;
            height: 28px;
            font-size: 22px;
            color: rgba(255, 255, 255, 1);
            span {
                font-size: 28px;
                text-decoration: underline;
            }
        }
    }
    .active {
        background: #ff5e06;
    }
}

3.2 顺时针旋转

布局有了,就要开始让每个格子切换背景色了,这里有一个问题,如何让九宫格顺时针旋转。比较特殊的是,这里中间有一个格子不用切换,其他的需要切换,所以上面的奖品数据不能直接修改active属性来切换样式,需要用一个变量记住“顺时针”的顺序。代码如下:

alongPointer: [1, 2, 3, 6, 9, 8, 7, 4]

这样就可以alongPointer变量中每个元素的值和luckyDrawList中的id属性一一对应,可以看到alongPointer数组中没有5,这是中间的点击按钮。如下图2。
image
图2

3.3 定时任务

定时任务是这个动画的关键,可以把这个顺时针旋转抽象为按照顺序执行的一系列异步操作。Promise是一种异步操作解决方案,可以把这些异步操作放在Promise中,并给定它的等待时间。代码如下:

startRoll() {
    let count = 5 * 8                           //8个奖品,先空转5圈
    let timeourID = null
    let tasks = []
    let exapsed = parseInt(1000 / 8)            //间隔时间
    const highlight = i =>
        new Promise((resolve, reject) => {
            this.timeourID = setTimeout(() => {
                this.luckyDrawList.forEach(u => {
                    u.active = u.id === this.alongPointer[i % 8] //轮播
                })
                resolve()
            }, exapsed * i)
        })
    //添加异步任务
    for (let i = 0; i < count; i++) {
        tasks.push(highlight(i))
    }
    //完成所有异步任务,执行后续操作
    Promise.all(tasks).then(() => {
        clearTimeout(this.timeoutID)
    })
}

上面代码中u.active = u.id === this.alongPointer[i % 8]这里不断轮播,i的值的范围是0~40,8个奖品的id保存在this.alongPointer变量中,用0到40和i取余,得到的是5组0,1,2,3,4,5,6,7,最后就空转了5圈。如下图3
image
图3

3.4 选中奖品

最后完成空转之后就可以定位奖品了,这里用一个随机函数getRandomIntInclusive来生成奖品,它生成一个大于等于min,小于等于max的随机数。代码如下:

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

所有异步任务完成之后,可以随机生成奖品了,使用Promise.all()来判断所有异步任务都执行完毕。代码如下:

Promise.all(tasks).then(() => {
    clearTimeout(this.timeoutID)
    this.showLotteryResult()
})
//设置奖品
showLotteryResult() {
  let randomIndex = this.getRandomIntInclusive(0, this.luckyDrawList.length - 1)
  let id = this.luckyDrawList[virtualIndex].id
  console.log(randomIndex, virtualIndex, id)
  this.luckyDrawList.forEach((u, i) => {
      u.active = u.id == id
  })
}

最后看看动画的效果,如下图4
image
图3

3.5 对Promise的改进

Promise的写法可以改进一下,使用async,await的方式来调用,这样代码看起来更加简洁。代码如下:

startRoll() {
    let count = 5 * 8                           //8个奖品,先空转5圈
    let interval = parseInt(1000 / 8)           //切换时间间隔
    let sleep = time => new Promise((resolve) => {
        this.timeoutID = setTimeout(resolve, time)
    })
    let pushEvent = async(count, interval) => {
        for (let i = 0; i < count; i++) {
            await sleep(interval)
            this.luckyDrawList.forEach(u => {
                u.active = u.id === this.alongPointer[i % 8]
            })
        }
    }
    pushEvent(count, interval).then(() => {
        clearTimeout(this.timeoutID)
        this.showLotteryResult()
    })
}

4. 总结

本文介绍了九宫格抽奖的实现方式,涉及到的知识点有Promise,Promise.all,async/await/then等,这里还可以优化一个功能,就是让九宫格先慢后快,最后再快速切换,下次有时间再研究。