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

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

構思

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

先完成一個 UI

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

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>
複製代碼

cssvue

.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;
            }
        }
    }
複製代碼

效果圖 git

開始作動畫效果

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

開始運行

動畫的速度是變化的,使用 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 取餘flex

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 項目實戰

歡迎關注公衆號「前端進階課」認真學前端,一塊兒進階。

相關文章
相關標籤/搜索