用vuejs擼一個抽獎元件

juejinyqj發表於2020-01-21

新年將至,先提前祝大家新年快樂萬事如意,想必很多人已經開過了一個記憶深刻或者毫無趣味的年會了,那麼,遲來的抽獎元件來了,233333333

用vuejs擼一個抽獎元件
先來一張效果圖,哈哈,不是什麼年會大抽獎,只是一個跑馬燈抽獎元件,233333,明年年會前,一定寫一篇年會用的抽獎

data

先來看看要使用到的一些data

data () {
    return {
      bgStatus: true, // 控制抽獎邊框的閃爍
      bgInterval: '', // 存放背景切換的setInterval,方便元件銷燬時清除任務
      prizeList: [], // 用於渲染的獎品列表
      idList: [], // 用於存放獎品id的陣列
      cur: undefined, // 當前步對應的項
      speed: 100, // 速度
      couldClick: true // 控制抽獎按鈕可點選狀態
    }
  }
複製程式碼

先看看data的一個結構,用到的變數註釋了,後面如果有疑問的可以到這邊來看

template

看下我們的template結構

<template>
  <div class="md-div">
    <!-----------跑馬燈------------->
    <div class="md-bd-bg" :class="bgStatus ? 'md-bd-bg1' : 'md-bd-bg2'">
        <!----------抽獎容器-------------->
      <div class="item-out">
        <!----------迴圈獎項-------------->
        <div class="prize-item flex-column flex-just-center flex-align-center" v-for="k in prizeList" :key="k.prizeid"
             :class="k.prizeid === cur ? 'prize-y' : 'prize-n'"
        >
          <div class="prize-img flex-row flex-just-end flex-align-center">
            <img :src="`https://${k.photo}`" alt="">
          </div>
<!--          <div class="prize-word">{{k.prize_name}}</div>-->
        </div>
        <!-----------抽獎按鈕------------->
        <div class="prize-btn" @click="doPrize" :class="couldClick ? '' : 'refuse-click'"></div>
      </div>
    </div>
  </div>
</template>
複製程式碼

分析一下結構,我們給了一個外層(class=md-bd-bg),用作外層燈光的閃爍控制,這裡很簡單,我們需要的只是兩張圖幾乎一樣的背景圖

用vuejs擼一個抽獎元件
無視馬賽克,我們只需要這樣的外邊框,另外一張圖也是這樣,只需要黃燈和白燈切換位置,即可做出閃爍的效果

.md-bd-bg1{background-image: url("../../statics/tbIcon/shine1.png")}
.md-bd-bg2{background-image: url("../../statics/tbIcon/shine2.png")}
.md-bd-bg{
  width: 100%;
  height: 100%;
  background-size: 100% 100%;
  padding: 6vw;
}
複製程式碼

這就是外邊框的樣式,md-bd-bg1和md-bd-bg2分別對應了兩個狀態,下一步就是要讓他動起來

    controlBg () { // 背景變換函式
      let vm = this
      vm.bgInterval = setInterval(() => {
        vm.bgStatus = !vm.bgStatus
      }, 500)
    }
複製程式碼

methods當中的一個方法,我們用bgInterval存放了一個setInterval,之所以要用一個變數儲存,是為了在清除元件的時候清除事件

正題來啦

終於到了我們正兒八經的跑馬燈抽獎了,先分析結構

 <!----------迴圈獎項-------------->
        <div class="prize-item flex-column flex-just-center flex-align-center" v-for="k in prizeList" :key="k.prizeid"
             :class="k.prizeid === cur ? 'prize-y' : 'prize-n'"
        >
          <div class="prize-img flex-row flex-just-end flex-align-center">
            <img :src="`https://${k.photo}`" alt="">
          </div>
<!--          <div class="prize-word">{{k.prize_name}}</div>-->
        </div>
複製程式碼

使用v-for將請求到的獎品陣列迴圈渲染

.prize-y{background-image: url("../../statics/tbIcon/prize_y.png");}
.prize-n{background-image: url("../../statics/tbIcon/prize_n.png")}
.prize-item:nth-of-type(1){top: .1vw;left: .1vw;}
.prize-item:nth-of-type(2){top: .1vw;left: 27.35vw;}
.prize-item:nth-of-type(3){top: .1vw;left: 54.6vw;}
.prize-item:nth-of-type(4){top: 27.35vw;left: 54.6vw;}
.prize-item:nth-of-type(5){top: 54.6vw;left: 54.6vw;}
.prize-item:nth-of-type(6){top: 54.6vw;left: 27.35vw;}
.prize-item:nth-of-type(7){top: 54.6vw;left: .1vw;}
.prize-item:nth-of-type(8){top: 27.35vw;left: .1vw;}
複製程式碼

這裡用到了vw單位,vw和vh、vmin、vmax這幾個,不說了,用了都說香,上面的程式碼將獎項擺放成了一個圈,留下了按鈕的空間,下面是點選按鈕的css

.prize-btn{width: 27vw;height: 27vw;background-size: 100% 100%;position: absolute;background-image: url("../../statics/tbIcon/prize-btn.png");top:27.35vw;left:27.35vw;}
複製程式碼

好啦,東西擺放好了,我們愉快地開始寫邏輯吧

點選抽獎

看看我們的抽獎按鈕

<template>
    <div class="prize-btn" @click="doPrize" :class="couldClick ? '' : 'refuse-click'"></div>
</template>
  /*------------------禁止點選-------------*/
.refuse-click{filter: grayscale(10%);}
    ||
   \||/
    \/
doPrize () {
      let vm = this
      if (vm.couldClick) {
        vm.couldClick = false // 禁止第一次抽獎沒執行完又進行下一次抽獎
        vm.$axios(urls.doPrize, {}).then(res => {
          let code = res.code
          if (code === 'success') {
            vm.prizeAnimate(res)
          } else {
            vm.$q.notify({ message: res.msg })
          }
        })
      }
    }
複製程式碼

這裡給了按鈕一個動態class,讓它在可點選的時候是設計圖上的原色,不可點選的時候則加上灰色的濾鏡,講道理,css的filter也是真的香,有興趣的同學可以去看看,可以看到點選了抽獎按鈕後,並不是直接執行prizeAnimate,而是先向服務請求了抽獎結果,也就是說,後面的動畫都是多餘的,你點選的時候已經決定了你的中獎結果,所以我們不寫動畫了,就這樣就可以直接用了

繼續

開個玩笑 粘上我的抽獎動畫程式碼

prizeAnimate (result) {
      let vm = this
      vm.cur = undefined
      let num = vm.idList.indexOf(Number(result.prizeid))
      let len = vm.idList.length
      let allSteps = Math.floor(Math.random() * 3 + 2) * len + num // 總共要走的步數 2~5圈
      let a = 0
      function myInt () {
        setTimeout(() => {
          if (a <= allSteps) { // a是已經跳動的步數
            if (allSteps - a < len * 2 && allSteps - a >= len) { // 進入倒數第二圈,增加延時
              vm.speed = 200
            } else if (allSteps - a < len) { // 進入倒數第一圈,再次增加延時
              vm.speed = 400
            }
            vm.cur = vm.idList[a % vm.idList.length] // 當前跳至的獎項可以用步數除以獎品陣列長度求餘即可
            a++ // 執行完跳動後記得將已跳動步數加1
            myInt() // 遞迴呼叫
          } else { // 終於跳完了
            vm.couldClick = true // 解除對按鈕的限制
            vm.speed = 100 // 將速度還原
            // -------
            // 抽獎動畫執行完接下來的操作,比如說彈窗通知中獎
          }
        }, vm.speed)
      }
      myInt()
    }
  }
複製程式碼

拿到了抽獎結果,第一步,我們將cur清空了,接著取出中獎結果在獎品陣列裡的index值,也就是num 此處的allSteps用來生成總共需要走的步數,為了讓動畫儘可能看起來‘我們是真的用動畫抽了的噢’,所以設定了2到5圈再加上前面求到的index值, 那麼現在我們就知道了總共要走的步數

那麼走起

這裡我宣告瞭myInt函式用作遞迴執行setTimeout的函式體,這樣做的目的,首先,我們的抽獎是有動畫的,所以速度肯定不能勻速,如果使用setInterval,那麼變更speed並不會改變動畫的跳動速度,其次嘛,setInterval其實也可以用,但是你得寫三個,一個快速的,一箇中速的,一個低速的,然後挨個執行,你看我大setTimeout遞迴呼叫,不香嗎,程式碼量不少嗎!哈哈,本來想再解釋一下這段函式,但是感覺都寫在備註裡了,那就這樣吧,我要收拾收拾回家過年了,886~~

整個元件奉上

<template>
  <div class="md-div">
    <div class="md-bd-bg" :class="bgStatus ? 'md-bd-bg1' : 'md-bd-bg2'">
      <div class="item-out">
        <div class="prize-item flex-column flex-just-center flex-align-center" v-for="k in prizeList" :key="k.prizeid"
             :class="k.prizeid === cur ? 'prize-y' : 'prize-n'"
        >
          <div class="prize-img flex-row flex-just-end flex-align-center">
            <img :src="`https://${k.photo}`" alt="">
          </div>
<!--          <div class="prize-word">{{k.prize_name}}</div>-->
        </div>
        <!-----------抽獎按鈕------------->
        <div class="prize-btn" @click="doPrize" :class="couldClick ? '' : 'refuse-click'"></div>
      </div>
    </div>
  </div>
</template>

<script>
import urls from 'src/api/urls'
export default {
  name: 'module',
  data () {
    return {
      bgStatus: true,
      bgInterval: '',
      prizeList: [], // 獎品列表
      idList: [],
      cur: undefined, // 當前幀對應的項
      speed: 100,
      couldClick: true // 抽獎按鈕可點選
    }
  },
  created () {
    let vm = this
    vm.controlBg() // 開啟背景變換
    vm.queryList() // 請求獎項
  },
  methods: {
    controlBg () { // 背景變換函式
      let vm = this
      vm.bgInterval = setInterval(() => {
        vm.bgStatus = !vm.bgStatus
      }, 500)
    },
    queryList () {
      let vm = this
      vm.$axios(urls.getPrizeList, {}).then(res => {
        let code = res.code
        if (code === 'success') {
          // console.log(res)
          for (let k in res.lottery_prize) {
            vm.idList.push(res.lottery_prize[k].prizeid)
          }
          vm.prizeList = res.lottery_prize
          vm.$emit('subNotice', res)
        } else {
          vm.$router.go(-1)
        }
      })
    },
    doPrize () {
      let vm = this
      if (vm.couldClick) {
        vm.couldClick = false // 禁止下次立即執行
        vm.$axios(urls.doPrize, {}).then(res => {
          let code = res.code
          if (code === 'success') {
            vm.prizeAnimate(res)
          } else {
            vm.$q.notify({ message: res.msg })
          }
        })
      }
    },
    prizeAnimate (result) {
      let vm = this
      vm.cur = undefined
      let num = vm.idList.indexOf(Number(result.prizeid))
      let len = vm.idList.length
      let allStamps = Math.floor(Math.random() * 3 + 2) * len + num // 總共要走的步數 2~5圈
      let a = 0
      function myInt () {
        setTimeout(() => {
          if (a <= allStamps) {
            if (allStamps - a < len * 2 && allStamps - a >= len) {
              vm.speed = 200
            } else if (allStamps - a < len) {
              vm.speed = 400
            }
            vm.cur = vm.idList[a % vm.idList.length]
            a++
            myInt()
          } else {
            vm.couldClick = true
            vm.$emit('subDc', result)
            vm.speed = 100
          }
        }, vm.speed)
      }
      myInt()
    }
  }
}
</script>

<style scoped>
.md-div{
  width: 94vw;
  height: 94vw;
  position: relative;
  margin: 3vw;
}
.md-bd-bg1{background-image: url("../../statics/tbIcon/shine1.png")}
.md-bd-bg2{background-image: url("../../statics/tbIcon/shine2.png")}
.md-bd-bg{
  width: 100%;
  height: 100%;
  background-size: 100% 100%;
  padding: 6vw;
}
.item-out{width: 100%;height: 100%;position: relative;}
.prize-item{
  width: 27vw;
  height: 27vw;
  background-size: 100% 100%;
  position: absolute;
}
.prize-btn{width: 27vw;height: 27vw;background-size: 100% 100%;position: absolute;background-image: url("../../statics/tbIcon/prize-btn.png");top:27.35vw;left:27.35vw;}
.prize-img{width: 70%;height: 40%;}
.prize-img img{width: 100%;height: auto;}
.prize-word{color: #832909;font-size: 1.4rem;}
.prize-y{background-image: url("../../statics/tbIcon/prize_y.png");}
.prize-n{background-image: url("../../statics/tbIcon/prize_n.png")}
.prize-item:nth-of-type(1){top: .1vw;left: .1vw;}
.prize-item:nth-of-type(2){top: .1vw;left: 27.35vw;}
.prize-item:nth-of-type(3){top: .1vw;left: 54.6vw;}
.prize-item:nth-of-type(4){top: 27.35vw;left: 54.6vw;}
.prize-item:nth-of-type(5){top: 54.6vw;left: 54.6vw;}
.prize-item:nth-of-type(6){top: 54.6vw;left: 27.35vw;}
.prize-item:nth-of-type(7){top: 54.6vw;left: .1vw;}
.prize-item:nth-of-type(8){top: 27.35vw;left: .1vw;}
  /*------------------禁止點選-------------*/
.refuse-click{filter: grayscale(10%);}
</style>

複製程式碼

相關文章