抽獎動畫 - 九宮格抽獎

nd發表於2021-08-10

本文介紹九宮格抽獎功能的實現。

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

相關文章