抽獎動畫 - 十二宮格抽獎

本文介紹一種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宮格抽獎功能,主要用到的技術有隨機打亂數組(洗牌演算法),定時任務,非同步任務隊列等。