本文介紹九宮格抽獎功能的實現。
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;
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。
圖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等,這裡還可以優化一個功能,就是讓九宮格先慢後快,最後再快速切換,下次有時間再研究。