手摸手。完成一個H5 抽獎功能


要完成一個這樣的抽獎功能

構思

  1. 獎勵物品是通過接口獲取的(img)
  2. 獎勵結果是通過接口獲取的(id)
  3. 抽獎的動畫需要由慢到快再到慢
  4. 抽獎轉動時間不能太短
  5. 抽獎結束需要回調
  6. 業務代碼和功能代碼要分離

先完成一個 UI

使用 flex 來布局,easy,當 curGameIdx 等於當前獎品 index 時高亮

html

    <div class="game-box">
        <template
        v-for="(val, idx) of boundList">
        <div v-if="idx == 4" class="game-item game-begin" 
            :key="idx"
            @click="beginGame">
            開始游戲
        </div>
        <div v-else :key="idx"
            class="game-item"
            :class="{
            active: idx === curGameIdx
            }">
            {{val}}
        </div>
        </template>
    </div>

css

    .game-box {
        display: flex;
        flex-wrap: wrap;
        text-align: center;
        .game-item {
            width: 1.25rem;
            height: 0.3rem;
            background: yellow;
            border: 1px solid transparent;
            transition: all 0.2s;
            &.game-begin {
                background: transparent;
            }
            &.active {
                border: 1px solid black;
            }
        }
    }

效果圖

開始做動畫效果

新建一個 Gameclass,有有個 run 方法和 finish 方法

開始運行

動畫的速度是變化的,使用 requestAnimationFramesetInterval 有點不妥,所以:可以使用 setTimeout + speed 參數 來控制動畫的速度。

class Game {
    constructor(idx) {
        this.idx = idx;
        this.speed = 400;
    }

    addIdx(){
    }

    speedControl() {
    }

    finish() {
    }

    run(cb) {
        this.speedControl();
        setTimeout(() => {
            this.addIdx();
            !this.isFinish && this.run(cb);
        }, this.speed);
    }
}

結束運行

收到結束運行的通知時,需要先做減速動畫,然后再停止在對應的 num,然后調用回調函數,所以先暫存結束回調和結束點,並將動畫設置為減速。

    finish(num, finishCb) {
        this.oil = false;
        this.endIdx = num;
        this.finishCb = finishCb;
    }

速度的控制

  1. 默認速度為加速(this.oil = true)通過是否達到預期速度來停止加速,當減速時同理。
  2. 為達到緩動結束效果,所以結束時間通過:到達最小速度 且 到達結束位置。
    speedUp() {
        this.speed -= 60;
    }

    speedDown() {
        this.speed += 200;
    }

    speedControl() {    
        if (this.speed > this.Max_Speed) {
            if (this.oil) {
                this.speedUp();
            }
        }
        if (!this.oil) {
            if (this.speed < this.Min_Speed) {
                this.speedDown();
            } else if (this.endIdx === this.idx) {
                this.isFinish = true;
                typeof this.finishCb === 'function' && this.finishCb();
            }
        }
    }

index 矯正

此時,上面 UI 是通過 v-for + flex 展示的,而動畫的執行是轉圈,所以需要矯正 index

更改上面 addIdx 方法,矯正 index,並將 ++index 取余

    constructor(idx) {
        this.idx = idx;
        this.speed = 400;
        this.order = null;
        this.Order_List = [0,1,2,5,8,7,6,3];
        this.Game_Box_Num = 8;
    }

    addIdx() {
        this.idx = (++this.idx % this.Game_Box_Num);
        this.order = this.Order_List[this.idx];
    }

活動代碼與業務代碼互動

將需要交互的函數傳遞給 Game 的實例即可

  // vue 代碼
  methods: {
    updateGameIdx(order) {
      this.curGameIdx = order; 
    },
    gameFinish() {
      this.playing = false;
      console.log(this.curGameIdx, 'curGameIdx')
    },
    beginGame() {
      if (this.playing) return;
      this.playing = true;
      this.curGameIdx = 0;
      const game = new Game(this.curGameIdx);
      game.run(this.updateGameIdx);
      // 通過請求終止
      setTimeout(() => {
        game.finish(2, this.gameFinish)
      }, 3000);
    }
  }

最后附上完整 Game 代碼:

class Game {
  constructor(idx) {
    this.idx = idx;
    this.speed = 400;
    this.oil = true;
    this.isFinish = false;
    this.endIdx = null;
    this.finishCb = function() {}
    // 常量
    this.Max_Speed = 100;
    this.Min_Speed = 500;
    this.Order_List = [0,1,2,5,8,7,6,3];
    this.Game_Box_Num = 8;
  }

  speedUp() {
    this.speed -= 60;
  }

  speedDown() {
    this.speed += 200;
  }

  speedControl() {
    if (this.speed > this.Max_Speed) {
      if (this.oil) {
        this.speedUp();
      }
    }
    if (!this.oil) {
      if (this.speed < this.Min_Speed) {
        this.speedDown();
      } else if (this.endIdx === this.idx) {
        this.isFinish = true;
        typeof this.finishCb === 'function' && this.finishCb();
      }
    }
  }

  finish(num, finishCb) {
    this.oil = false;
    this.endIdx = num;
    this.finishCb = finishCb;
  }

  addIdx() {
    this.idx = (++this.idx % this.Game_Box_Num);
  }

  run(cb) {
    this.speedControl();
    typeof cb === 'function' && cb(this.Order_List[this.idx]);
    setTimeout(() => {
      this.addIdx();
      !this.isFinish && this.run(cb);
    }, this.speed);
  }
}

export default Game;

大致效果

主要功能已經實現,想漂亮點再改改 CSS 就好了,動畫時間也需要再調試。(避嫌,具體結果不能提供 - -。)

最后

譯者寫了一個 React + Hooks 的 UI 庫,方便大家學習和使用,

React + Hooks 項目實戰

歡迎關注公眾號「前端進階課」認真學前端,一起進階。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM