用 60 行代碼實現一個高性能的聖誕抽抽樂 H5 小遊戲

一個人幾乎可以在任何他懷有無限熱忱的事情上成功.

效果展示

將收穫

• 防抖函數的應用 • 用 css 實現九宮格佈局 • 生成 n 維環形座標的算法 • 如何實現環形隨機軌道運動函數 • 實現加速度動畫 • 性能分析與優化

設計思路

具體實現

由於目前已有很多方案可以實現九宮格抽獎動畫, 比如使用動態 active 實現邊框動畫, 用隨機算法和定時器設置在何處停止等等. 爲了進一步提高性能, 本文介紹的方法, 將使用座標法, 將操作 dom 的成本降低, 完全由 js 實現滑塊的路徑的計算, 滑塊元素採用絕對定位, 讓其脫離文檔流, 避免其他元素的重繪等等, 最後點擊按鈕我們會使用防抖函數來避免頻繁執行函數, 造成不必要的性能損失.

1. 九宮格佈局實現

爲了讓大家更加熟悉 dom 結構, 這裏我就不用 js 動態生成了. 如下 html 結構:

<div>
    <div>聖誕抽抽樂</div>
    <div>
        <div>我愛你</div>
        <div>你愛我</div>
        <div>我不愛你</div>
        <div>你愛我</div>
        <div>開始</div>
        <div>你愛我</div>
        <div>再見</div>
        <div>謝謝惠顧</div>
        <div>你愛我</div>
        <div></div>
    </div>
</div>

九宮格佈局我們使用 flex 來實現, 核心代碼如下:

.box {
    display: flex;
    flex-wrap: wrap;
    width: 300px;
    height: 300px;
    position: relative;
    .item {
        box-sizing: border-box;
        width: 100px;
    }
    // 滑塊
    .spin {
        box-sizing: border-box;
        position: absolute;
        left: 0;
        top: 0;
        display: inline-block;
        width: 100px;
        height: 100px;
        background-color: rgba(0,0,0,.2);
    }
}

由上可知容器 box 採用 flex 佈局, 要想讓 flex 子元素換行, 我們這裏要設置 flex-wrap: wrap; 此時九宮格佈局就實現了. 滑塊採用絕對定位, 至於具體如何去沿着環形軌道運動, 請繼續看下文介紹.

2. 生成 n 維環形座標的算法

由上圖我們可以知道, 一個九宮格的 4 條邊, 可以用以上 8 個座標收尾連接起來, 那麼我們可以基於這個規律. 來生成環形座標集合. 代碼如下:

/**
 * 生成n維環形座標
 * @param {number} n 維度
 * @param {number} cell 單位座標長度
 */
function generateCirclePath(n, cell) {
  let arr = []
  for(let i=0; i< n; i++) {
      arr.push([i*cell, 0])
  }
  for(let i=0; i< n-1; i++) {
      arr.push([(n-1)*cell, (i+1)*cell])
  }
  for(let i=0; i< n-1; i++) {
      arr.push([(n-i-2)*cell, (n-1)*cell])
  }
  for(let i=0; i< n-2; i++) {
      arr.push([0, (n-i-2)*cell])
  }
  return arr
}

如果是單位座標, 那麼 cell 爲 1,cell 設計的目的就位爲了和現實的元素相結合, 我們可以手動設置單元格的寬度來實現不同大小的 n 維環形座標集.

3. 實現環形隨機軌道運動函數

由抽獎動畫分析可知, 我們滑塊運動的軌跡, 其實就是環形座標集合, 所以我們只要讓滑塊的頂點 (默認左上角) 沿着環形座標集合一步步變化就好了.

function run(el, path, n = 1, i = 0, len = path.length) {
    setTimeout(() => {
        if(n > 0) {
          if(len <= i) {
              i = n === 1 ? len : 0
              n--
          }
          el.css('transform', `translate(${path[i][0]}px, ${path[i][1]}px)`)
          run(el, path, n, ++i, len)
        }
    }, 300)   
}

這樣就能實現我們的滑塊按照九宮格邊框運動的動畫了, 當然以上函數只是基本的動畫, 還沒有實現在隨機位置停止, 以及滑塊的加速度運動, 這塊需要一定的技巧和 js 基礎知識比如閉包.

3.1 加速度運動

加速度運動其實很簡單, 比如每轉過一圈將 setTimeout 的延遲時間改變即可. 代碼如下:

function run(el, path, n = 1, speed = 60, i = 0, len = path.length) {
    setTimeout(() => {
        if(n > 0) {
          if(len <= i) {
              i = n === 1 ? len : 0
              n--
              speed += (300 - speed) / n
          }
          el.css('transform', `translate(${path[i][0]}px, ${path[i][1]}px)`)
          run(el, path, n, speed, ++i, len)
        }
    }, speed)   
}

3.2 隨機停止實現

隨機停止這塊主要是用了 Math.random 這個 API, 我們在最後一圈的時候, 根據隨機返回的數值來決定何時停止, 這裏我們在函數內部實現隨機數值, 完整代碼如下:

/**
* 環形隨機軌道運動函數
* @param {element} el 運動的dom元素
* @param {array} path 運動的環形座標集合
* @param {number} speed 運動的初始速度
* @param {number} i 運動的初始位置
* @param {number} len 路徑的長度
* @param {number} random 中獎座標
*/
function run(el, path, n = 1, speed = 60, i = 0, len = path.length, random = Math.floor(Math.random() * len)) {
    setTimeout(() => {
        if(n > 0) {
          // 如果n爲1,則設置中獎數值
          if(n === 1) {
            len = random
          }
          if(len <= i) {
              i = n === 1 ? len : 0
              n--
              speed += (300 - speed) / n
          }
          el.css('transform', `translate(${path[i][0]}px, ${path[i][1]}px)`)
          run(el, path, n, speed, ++i, len, random)
        }
    }, speed)   
}

4. 實現點擊開始的防抖函數以及應用

防抖函數實現:

// 防抖函數,避免頻繁點擊執行多次函數
function debounce(fn, interval = 300) {
  let timeout = null
  return function () {
      clearTimeout(timeout)
      timeout = setTimeout(() => {
          fn.apply(this, arguments)
      }, interval)
  }
}

那麼我們點擊時, 代碼應該長這樣:

// 點擊開始按鈕,開始抽獎
$('.start').on('click',debounce(() => { run($('.spin'), generateCirclePath(3, 100), 3) }))

總結

該實現方式的好處是支持 n 維環形座標的抽獎, 基於座標法的應用還有很多, 尤其是遊戲和圖形領域, 在實現過程中一定要考慮性能和可擴展性, 這樣我們就可以在不同場景使用同一套方法論, 豈不樂哉? 本文完整源碼我會放在 github 上, 歡迎交流學習~

最後

如果想了解更多 H5 遊戲, webpacknodegulpcss3javascriptnodeJScanvas 數據可視化等前端知識和實戰,關注《趣談前端》加入我們一起學習討論,共同探索前端的邊界。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/-PuKEcrTWBCUtWcqs5VUHA