抽獎動畫 - 大轉盤抽獎

1.需求

抽獎是各種營銷活動中最多見的一種形式,本產品需求大體以下:轉盤周圍跑馬燈交替閃爍,點擊抽獎,大轉盤旋轉,調用接口獲取抽獎結果,大轉盤指針指向對應的獎品。高保以下圖1css

圖1-高保html

2.總體思路

本需求要求跑馬燈交替閃爍,那四周的跑馬燈就不能是死的圖片了,要用動畫來實現,而且第奇數,偶數交替變換,這個使用vue中的動態屬性能夠實現。vue

其次小燈泡分佈在四周,首先想到的是 transform: rotate(); 而後獎品圖片也是分佈在圓形四周,這個也能夠用 transform: rotate()。jquery

最後點擊當即抽獎,包含獎品圖片的整個dom旋轉,這個使用animation+transform: rotate();能夠實現。後端

3.實現過程

各位看官請注意,這裏只介紹了關鍵實現過程,中間涉及到的佈局是大多使用absolute定位來實現的,中獎彈框是另一個組件,由於不是關鍵,因此沒有具體介紹,也沒有貼出代碼。數組

3.1背景

這個背景通常是UI給個圖片出來,雖然使用css能夠實現複雜的圖形,可是要花很長時間得不償失,通常給個背景圖片就能夠了。注意這裏高保給的有問題,後面獎品的dom會用另一張背景覆蓋。以下圖2瀏覽器

圖2-轉盤背景dom

3.2轉盤跑馬燈

跑馬燈是一個一個的燈泡,放在轉盤周圍,這個要根據高保尺寸來寫,否則距離有差,無法落在四周邊緣的位置。還有小燈泡明暗交替放置,否則也沒有效果。html代碼以下:函數

<!-- 轉盤周圍跑馬燈 -->
<div class="lightWrap">
  <div
    v-for="i in 20"
    :key="i"
    :style="setLightRotate(i)"
    class="lightItem">
    <img
      v-if="i % 2 == 0"
      :class="{active: !lightChange}"
      :src="lightChange ? lightGrey : lightActive"
      alt=""/>
    <img
      v-else
      :class="{active: lightChange}"
      :src="!lightChange ? lightGrey : lightActive"
      alt=""/>
  </div>
</div>

注意周圍有20個燈泡,因此使用v-for="i in 20",而後setLightToatate(i)是一個方法,用來設置每一個燈泡的傾斜度,以下:佈局

//20個燈泡設置傾斜
setLightRotate(index) {
  let lightRotate = (360 / 20) * index
  return {
    transform: 'rotate(' + lightRotate + 'deg)'
  }
}

這個很容易理解了,整個圓是360度,除以20,是每兩個圓之間的間隔角度,再乘以數組20的下標,就是每一個燈泡的偏移角度。

小燈泡的顯示就須要交替顯示了,計算奇偶使用表達式i%2 == 0,而後使用一個變量lightChange來判斷當前這個燈泡是不是點亮,來區分顯示不一樣的圖片。這個變量是經過questAnimationFrame遞歸調用來修改。方法以下:

//瀏覽器播放跑馬燈動畫
setTimeLine() {
  this.lightCount += 1
  // 瀏覽器渲染頻率 60幀/s 約等於 16.66ms 一次,取20的倍數,就是約300ms切換跑馬燈一次
  if ((this.lightCount % 20) == 0) {
    this.lightChange = !this.lightChange
  }
  requestAnimationFrame(this.setTimeLine)
}

在mounted鉤子裏調用一次setTimeLine()方法,而後在方法裏調用requestAnimationFrame(),可是在requestAnimation()的回調函數裏又調用了本身。注意這種方式相似遞歸調用,可是不是遞歸調用,瀏覽器的渲染評率是60幀每秒,也就是requestAnimationFrame()的回調函數在1000毫秒/60=16.66毫秒,也就是每16.66毫秒就執行一次setTimeLine()方法,在方法裏lightChange自增1,判斷lightCount是20的倍數切換lightChange變量,而後16.66毫秒*20=33.33毫秒切換一次。

lightChange變量還切換了當前燈泡的樣式,點亮後還會設置燈泡變大一點。css以下:

.lightWrap {
  width: $turntableWrap_size;
  height: $turntableWrap_size;
  position: absolute;
  left: 50%;
  top: 50%;
  margin-left: calc(#{$turntableWrap_size} / -2);
  margin-top: calc(#{$turntableWrap_size} / -2);
  .lightItem {
    width: 22px;
    height: calc(#{$turntableWrap_size} / 2);
    position: absolute;
    left: 50%;
    top: 0%;
    transform-origin: 0 calc(#{$turntableWrap_size} / 2);

    img {
      width: 22px;
      height: 22px;
      position: absolute;
      top: 10px;
      left: 0;
    }
    img.active {
      width: 40px;
      height: 40px;
      position: absolute;
      top: 1px;
      left: -9px;
    }
  }
}

最終效果以下圖3

圖3

3.3獎品圖片

接下來是要把獎品圖片放在轉盤上,而且分佈在轉盤四周,原理仍是使用transform: rotate();方法來設置傾斜。html代碼以下:

<!-- 獎品圖片 -->
<div class="circleMax" :class="{ani: runningLock}">
  <div
    v-for="(item, i) in actPrizeList"
    :key="i"
    :style="setRotate(i)"
    class="spin">
    <div :style="setSpinInner(i)" class="spinInner">
      <div :style="spinCntDocObj" class="spinCntDoc">
        <div class="spinImg">
          <img :src="item.imgUrl" alt="" srcset="">
        </div>
      </div>
    </div>
  </div>
</div>

actPrizeList就是獎品信息了,這個是從接口獲取,裏面有配置好的獎品圖片鏈接。這裏仍是使用了一個方法setTotate(i)來動態設置樣式,方法以下:

/* 獎品圖片傾斜 */
setRotate(index) {
  let spinRotate = this.jiaodu * index
  return {
    transform: 'rotate(' + spinRotate + 'deg)'
  }
}

setSpinInner()方法的功能相似,以下:

setSpinInner(index) {
  return {
    transform: 'rotate(-' + this.jiaodu + 'deg)',
    borderLeft: 0
  }
}

這裏的變量jiaodu是45,是根據360度/8個獎品的規則來的,用來調整獎品傾斜度。這裏還有一個spinCntDocObj對象,用來設置獎品圖片容器的尺寸,這個是爲了在某些需求不是顯示獎品圖片,而是獎品名稱的時候,或者即顯示獎品名稱,又顯示獎品圖片的時候佈局方便,固然在這裏只顯示了一個獎品圖片。以下圖4

圖4

具體設置方法根據內圈直徑計算容器寬度,代碼以下:

/* 圖片旋轉 */
setSpinCntDoc() {
  let spinCntDocWidth = (Math.sin((this.jiaodu / 2) * (Math.PI / 180)) * this.tableInnerSize) / 70
  this.spinCntDocObj.width = spinCntDocWidth + 'rem'
  this.spinCntDocObj.textAlign = 'center'
  this.spinCntDocObj.transform = 'rotate(' + (this.jiaodu / 2) + 'deg)'
}

3.4背景總體旋轉

跑馬燈有了,獎品也有了,剩下就是要背景總體旋轉起來了。細心的話,你會發如今全部獎品容器上有一個動態樣式:class="{ani: runningLock}",變量runningLock這個變量是用來控制大轉盤旋轉的,大轉盤中全部獎品容器以下圖5:

圖5

css類anti中包含一個animation動畫,以下:

  .ani {
    animation: circle 3s ease forwards;
  }

由於最後要根據中獎的獎品來計算大轉盤具體傾斜的角度,因此這個關鍵幀circle須要在請求接口以後,經過js代碼動態加載到頁面上,下面講抽獎按鈕的時候會具體的說明。

3.5抽獎按鈕

接下來須要把抽獎按鈕放在大轉盤正中間,仍是使用相對定位absolute來實現,html代碼以下:

<!-- 抽獎按鈕 -->
<div
  class="arrowBtn"
  :class="{btnShake: btnShakeShow}"
  @click="startClick">
  <img src="../assets/images/summer/btn-draw.png" alt=""/>
</div>

css代碼以下

.arrowBtn {
  width: 216px;
  height: 260px;
  border-radius: 95px;
  text-align: center;
  position: absolute;
  left: 50%;
  top: 50%;
  margin-left: -108px;
  margin-top: -152px;
  img {
    width: 100%;
  }
}

在點擊按鈕的時候也有一個動畫,就是按鈕會變大而後變小,看起開是彈了一下,這個就簡單了,使用animation動畫就好,這裏經過btnShakeShow變量來控制,css代碼以下:

.btnShake {
  animation: btnShakeAni 0.5s ease-out forwards;
}
@keyframes btnShakeAni {
  0% {
    transform: scale(1);
  }
  10% {
    transform: scale(1.1);
  }
  30% {
    transform: scale(0.9);
  }
  50% {
    transform: scale(1.1);
  }
  70% {
    transform: scale(0.9);
  }
  90% {
    transform: scale(1.1);
  }
  100% {
    transform: scale(1);
  }
}

最後效果以下圖6

圖6

3.6點擊抽獎

上面3.4講到在css類ani中使用animation動畫circle來控制整個大轉盤旋轉,而且要根據接口返回的抽獎結果計算旋轉角度動態設置rotate角度。代碼以下

/* 點擊抽獎播放動畫 */
startClick() {
  //大轉盤旋轉
  this.runningLock = true
  //抽獎按鈕彈一下
  this.btnShakeShow = true
  //調接口
  let data = {actCode: actCode}
  coc2.drawLottery(data).then(res => {
    if (res.code == 0) {
      if (res.data) {
        this.prizeNum = this.actPrizeList.findIndex((item, index) => item.pid == res.data.pid)
        this.prizeName = res.data.prizeName
        this.prizeImgSrc = res.data.litimgUrl
      }
    }
    //動態加載動畫關鍵幀
    let targetDeg = 360 * this.defaultRunTimes + (this.spinNum - this.prizeNum) * this.jiaodu + this.jiaodu * 0.5
    let runkeyframes = `@keyframes circle{ 0% {transform: rotate(0deg);} 100% {transform: rotate(${targetDeg}deg); }`
    document.getElementById('mystyle').innerHTML = runkeyframes
    setTimeout(() => {
      this.$refs.refAlert.show('getPrize')
    }, 3500)
  })
}

注意spinNum是轉盤中全部獎品個數,prizeNum是中獎獎品在整個獎品中數組中的下標,兩者作減法,而後頭部加上一個轉盤默認要轉圈數,尾部加上一個偏移(360度/8=45度)就能夠定位到相應的位置的角度。隨後就是用這個角度拼接關鍵幀,最後動態設置這個css關鍵幀。注意要在index.html中加上一個id爲mystyle的style元素,html以下:

圖7

整個動畫部分已經完成,來看看總體效果是怎麼樣的,以下圖7

圖7

3.7動畫復原&中獎彈框

最後還有一個問題,抽獎以後須要不管是否中獎都須要將轉盤復原到初始狀態,這個動做的觸發時機在中獎彈框彈出以後,這樣方便下一次抽獎。實現這個功能須要在點擊獎品彈框的時候使用回調的方式。最後中獎的獎品圖片和獎品名稱也須要經過屬性賦值傳遞給獎品彈框。上面代碼中有的this.prizeName = res.data.prizeName;this.prizeImgSrc = res.data.litimgUrl就是在作這個事情。下面的html代碼。

<!-- 中獎彈框 -->
<dialog-alert
  ref="refAlert"
  :prize-img-src="prizeImgSrc"
  :prize-name="prizeName"></dialog-alert>

在dialog-alert組件中,會有一個事件回調,這裏使用的是eventbus,緣由是這個組件在多個地方調用,這個和本文的主題關係不大 ,只簡單提一下。組件中回調方法以下:

EventBus.$emit("turntableReset")

當前抽獎組件中監聽方法以下:

mounted() {
  // 彈窗關閉 重置大轉盤
  EventBus.$on("turntableReset", () => this.turnTableReset())
},
methods{
  //轉盤數據重置
  turnTableReset() {
    this.startLock = false
    this.runningLock = false
    this.btnShakeShow = false
  }
}

最後看看總體效果,以下圖8

圖8

4.總結

本功能還涉及到其餘的功能,本功能實現的有些倉促,還有不少能夠改進的地方。例如在播放動畫以前先請求了接口,等後端有了響應纔開始播放動畫,這個不太合理,應該是先播放一個動畫,等有結果以後,再播放第二個動畫,讓指針指向中獎獎品。可使用jquery動畫,或者tween.js,下次有時間再研究。

相關文章
相關標籤/搜索