本文介绍九宫格抽奖功能的实现。
1.需求
功能很简单,来看看高保截图,如下图1
图1
需求的功能点如下:
- 用户点击抽奖,九宫格四周的图片顺时针依次闪烁,空转几圈。
- 请求接口,等接口有返回后最后对应的奖品闪烁,其他奖品不闪烁。
- 登录后,正中间的抽奖这个小方格点亮,未登录是灰色,这一点和抽奖无关,本文不做介绍。
- 最后弹框弹出抽奖结果。
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;
530px;
height: 519px;
@include flex(row, normal, normal, wrap, space-between);
.lucky-draw-item {
position: relative;
176px;
height: 171px;
border-radius: 20px;
color: #fff;
vertical-align: bottom;
img {
160px;
height: 157px;
}
.start-tips-times {
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 40px;
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。
图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
图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
图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等,这里还可以优化一个功能,就是让九宫格先慢后快,最后再快速切换,下次有时间再研究。