<input id="ohw05"></input>
  • <table id="ohw05"><menu id="ohw05"></menu></table>
  • <var id="ohw05"></var>
  • <code id="ohw05"><cite id="ohw05"></cite></code>
    <label id="ohw05"></label>
    <var id="ohw05"></var>
  • 抽獎動畫 - 鯉魚跳龍門


    pageClass: home-page-class

    鯉魚跳龍門動畫

    1. 需求

    年中618營銷活動要求做一個鯉魚跳龍門的動畫,產品參考了支付寶上的一個動畫,要求模仿這個來做一個類似的動畫。產品提供的截屏視頻如下:

    圖1
    從這個視頻里得到的信息,我們可以把動畫分解一下:

    • 321倒計時結束,動畫開始播放。
    • 小河背景向下滾動,看上去小魚在不停的向上游動,其實小魚固定在屏幕中間位置。
    • 金幣從屏幕頂部掉落,掉入小魚的嘴里的時候金幣消失,金幣在掉落同時金幣在旋轉。
    • 用戶點擊“狂點”按鈕,該按鈕四周會出現一個光暈,并且變大變小。
    • 金幣掉落完畢,出現龍門,小魚跑到龍門上方。
    • 播放動畫同時頂部有一個時鐘倒計時,從6.18倒數到0。

    從視頻上看,有一部分用css動畫實現起來比較麻煩,例如,金幣掉落完成之后,小魚要轉身,從背對觀眾變成面向觀眾,同時大小在變化,這些常見的css動畫沒法完全復原,初步判斷這些是使用其他動畫庫來實現的,普通的css動畫無法實現。
    我們事先要把這些告知產品,不然最后實現起來非常麻煩,因為本身活動項目開發時間非常短。

    2. 整體思路

    2.1 三二一倒計時

    三二一倒計時這個很簡單,直接用文字顯示的話不太美觀,UI提供了三個4個張圖片,我們可以按照數字分別命名3.png,2.png,1.png,0.png,然后使用setTimeInterval給變量做遞減就可以了。倒計時結束后靜態的小魚變成一個游泳的小魚,這里是一個gif圖片,所以直接使用切換圖片就可以了。

    2.2 河流

    小魚向下游動,相對而言可以讓小河向上滾動,在游戲背景上讓河流絕對定位,設置position,初始bottom為0,播放動畫,變為top為0,這樣看上去是小魚向上游動。

    2.3 金幣墜落

    金幣墜落也是使用絕對定位的方式,初始狀態top是負值,隱藏在屏幕最上方,下落過程中逐漸變小,并且有旋轉的動作,這里使用rotateY來控制旋轉。待金幣墜落到小魚嘴的位置的時候,金幣消失,模擬小魚吃掉金幣,這里設置大小為0,使用scale來縮放圖片實現。

    2.4 “狂點”按鈕

    用戶點擊狂點按鈕時,小魚的背后出現一個光暈,它由大變小,再由小變大,看上去小魚是在加速,這個交互可以讓動畫更加生動。點擊狂點按鈕是,這個按鈕自己本身也有一個由小變大,再由大變小的過程。

    2.5 跳龍門

    整個跳龍門的時間控制在6.18秒內,也就是河流滾動的時間也是6.18秒,結束后背景上面出現一個龍門圖片,小魚跳出屏幕。龍門圖片最初設置opacity是0,跳出后是1,這樣自然過度,如果使用顯示&影藏來控制,看上去有點突兀。

    2.6 時鐘

    最后頂部的倒計時時鐘就很簡單了,只要控制一個數字從6.18遞減到0就滿足需求了。

    3. 實現過程

    3.1 布局

    整個布局思路是絕對定位,整個背景fix定位在整個屏幕上,其他的元素使用absolute定位來固定位置。注意背景內的元素是absolute定位,都是居中顯示,這里使用常用的方式left: 50%; margin-left: -(width/2);來設置左右居中。布局如下圖1:

    圖2 布局
    初始狀態是這樣,注意狂點按鈕覆蓋在小魚上方,這個可以使用不同的z-index來實現,還有一些隱藏的元素,例如:金幣圖片,龍門圖片,動畫未開始的時候他么是隱藏的。

    html代碼如下:

    <!-- 躍龍門游戲 -->
    <div class="dragon-gate-game" @touchmove.prevent.stop @mousewheel.prevent>
      <!-- 321倒計時 -->
      <mask-dialog ref="refCountdown">
        <div class="count-down">
          <img v-show="countDown == 3" class="coupon-btn" :src="require('../assets/images/animation/3.png')" alt="" />
          <img v-show="countDown == 2" class="coupon-btn" :src="require('../assets/images/animation/2.png')" alt="" />
          <img v-show="countDown == 1" class="coupon-btn" :src="require('../assets/images/animation/1.png')" alt="" />
          <img v-show="countDown == 0" class="coupon-btn" :src="require('../assets/images/animation/0.png')" alt="" />
        </div>
      </mask-dialog>
    
      <!-- 跳龍門 -->
      <div class="jump">
        <!-- 時鐘倒計時 -->
        <div class="clock">{{ game.clock }}</div>
        <!-- 福字 -->
        <img v-for="(img, i) in game.blessing" :key="i" :src="img" class="blessing" alt="" />
        <!-- 小魚 -->
        <div :class="[fish.name]" id="fish">
          <img :src="fish.src" alt="" class="img-fish"/>
          <img src="../assets/images/animation/bg-aureole.png" alt="" class="backdrop">
        </div>
        <!-- 狂點按鈕 -->
        <img src="../assets/images/animation/btn1.png" :data-name="fish.name" alt="" class="btn-click" @click="jump" />
        <!-- 龍門 -->
        <img src="../assets/images/animation/bg-door.png" alt="" class="door" />
        <!-- 河 -->
        <img src="../assets/images/animation/bg-animation.jpg" alt="" class="river" />
      </div>
    </div>
    

    給背景div設置禁止滾輪滾動,禁止拖放,防止它出現滾動條,配合fix定位,固定在屏幕上。其他的元素使用absolute定位,這里有兩個兼容性問題要注意:

    • 注意元素定位使用bottom,不能使用top,防止部分瀏覽器底部工具欄遮擋"狂點"按鈕,其他的元素也使用bottom。
    • 注意321倒計時不能使用js動態切換圖片的路徑,而是使用v-show判斷,否則切換瀏覽器的時候在低端瀏覽器上會出現屏幕閃爍的現象,估計是造成頁面重繪了。
      css代碼如下:
    .dragon-gate-game {
      position: fixed;
      top: 0;
      left: 0;
      bottom: 0;
      right: 0;
      z-index: 1;
      .loading,  .dragon-gate-game, .count-down, .jump {
        width: 100%;
        height: 100vh;
      }
      .count-down  {
        @include flex(center, center, row, nowrap);
        .coupon-btn {
          width: 400px;
        }
      }
      .jump {
        position: relative;
        overflow: hidden;
        .river, .clock, .water, .fish, .swim-fish, .btn-click, .door, .blessing {
          position: absolute;
          bottom: 0;
          left: 0;
          right: 0;
        }
        .clock {
          width: 239px;
          height: 64px;
          background: 34px center / 44px 44px no-repeat url("../assets/images/animation/icon-clock.png"), #000000;
          opacity: 0.4;
          border-radius: 32px;
          top: 120px;
          left: 50%;
          margin-left: -119px;
          z-index: 3;
          font-size: 36px;
          font-weight: 400;
          color: #FFFFFF;
          line-height: 64px;
          text-indent: 44px;
        }
        .river {
          z-index: 1;
          width: 750px;
          // height: 14039px;
        }
        .door {
          width: 750px;
          height: 960px;
          z-index: 3;
          opacity: 0;
        }
        .water {
          z-index: 2;
          width: 750px;
          height: 467px;
        }
        .fish, .swim-fish {
          position: relative;
          z-index: 3;
          left: 50%;
          img {
            position: absolute;
            width: 100%;
            left: 50%;
            margin-left: -50%;
          }
          .img-fish {
            z-index: 3;
          }
          .backdrop {
            position: absolute;
            z-index: 2;
            left: 50%;
            margin-left: -50%;
            opacity: 0;
          }
        }
        .fish {
          width: 259px;
          margin-left: -129px;
          top: 700px;
        }
        .swim-fish {
          width: 259px;
          margin-left: -129px;
          top: 600px;
        }
    
        .btn-click {
          z-index: 4;
          width: 240px;
          left: 50%;
          margin-left: -120px;
          bottom: 200px;
          // top: 1000px;
          // animation: .4s linear 1s infinite alternate btnZoom;
        }
        @keyframes btnZoom {
          from {
            transform: scale(0.8);
          }
          to {
            transform: scale(1.1);
          }
        }
    
        .blessing {
          width: 80px;
          margin-left: -40px;
          z-index: 3;
          left: 50%;
          top: -140px;
        }
      }
    }
    

    3.2 倒計時

    data中定義變量countDown,初始值是3,使用setInterval來遞減這個變量,這個邏輯相對來說比較簡單,代碼如下:

    //倒計時
    countDownClock() {
      this.$refs.refCountdown && this.$refs.refCountdown.show()
      this.timerInterval = null
      this.timerInterval = setInterval(() => {
        this.countDown--
        if (this.countDown < 0) {
          clearInterval(this.timerInterval)
          this.timerInterval = null
          this.$refs.refCountdown &&this.$refs.refCountdown.hidden()
          this.countDown = 3
          // 切換動畫魚
          // this.fish = this.game.swimFish
          // 播放動畫
          // this.playAnime()
        }
      }, 1100)
    }
    

    倒計時我們也放在一個透明蒙層里,最后兩句切換動畫魚和靜態魚圖片和播放小河,金幣動畫,暫時注釋了,來看看效果:

    圖3 倒計時

    3.3 播放動畫

    開始播放動畫時,首先把小魚切換成那個gif圖片,讓小魚動起來,這里在data數據中定義了一些數據。

    data(){
      return {
        pageShow: '',                       //頁面顯示
        percentage: '2%',                   //進度條變化
        countDown: 3,                       //321倒計時
        timerInterval: null,                //計時器,用于清除
        fish: {},                           //當前顯示小魚
        game: {
          finish: false,                    //是否已完成,回調后不能再點
          clock: 6.18,                      //時鐘倒計時
          duration: 6180,                   //動畫持續時間
          blessingOpacity: '1',             //顯示金幣
          fish: {name: 'fish', src: require('../assets/images/animation/bg-fish.png')},               //小魚圖片
          swimFish: {name: 'swim-fish', src: require('../assets/images/animation/fish-swim.gif')},    //游泳的小魚
          blessing: Array(20).fill(require('../assets/images/losing-lottery/text-blessing.png')),      //金幣
          clickCount: 0,                  //點擊次數
        }
      }
    }
    

    切換小魚只需要上面注釋的那句就可以了:this.fish = this.game.swimFish,然后執行下面的this.playAnime()來播放動畫。
    這里還是使用anime.js動畫庫來播放,首先讓小河向上滾動,同時讓時鐘從6.18倒數到0,同時讓金幣墜落,這三個動畫前兩個動畫的時間是一致的,都是6.18秒,金幣墜落的動畫需要自己來估計,這里使用一個延遲,交錯動畫,延遲時間6.18*0.12,交錯時間200毫秒,同時這個還和金幣個數有關系,如果金幣太少,動畫后半部分沒有金幣墜落,金幣太多6.18秒過了金幣還沒有落完,這都不是我們想要的結果,我們設置金幣總共個數是20。
    6.18秒結束時要讓龍門浮出,小魚跳出龍門,龍門浮出通過設置opacity來實現,小魚跳出,通過translateY實現,最后看代碼如下:

    playAnime() {
      let tl = anime.timeline()
      //動畫
      tl.add({
        //河流流動
        targets: '.river',
        easing: 'linear',
        duration: this.game.duration,
        top: 0,
        complete: () => {
          this.game.finish = true
          this.$emit('animeFinish', this.game.clickCount)
        }
      }).add({
        targets: this.game,
        clock: 0,
        easing: 'linear',
        round: 100,
        duration: this.game.duration,
      }, 0).add({
        //金幣下落
        targets: '.blessing',
        easing: 'linear',
        delay: anime.stagger(200, {start: this.game.duration * 0.12}),
        keyframes: [
          {top: '30%', opacity: '1', scale: 0.8},
          {top: '45%', opacity: '0', scale: 0.5, rotateY: '360deg'}
        ],
      }, 0).add({
        //龍門浮出
        targets: '.door',
        easing: 'linear',
        delay: 200,
        opacity: 1
      }).add({
        //魚跳出去
        targets: '#fish',
        // translateY: -100,
        translateY: -550,
        duration: 1000
      })
    }
    

    結合data數據來看,前兩個動畫持續時間都是this.game.duration也就是6.18,金幣墜落的動畫需要我們調試,這里還使用了關鍵幀,動畫進度是30%的時候,金幣透明度是1,大小為原始大小的0.8倍,進度為45%的時候opacity是0,scale是0.5,沿Y軸旋轉360度。金幣墜落完成后龍門浮出,小魚跳過龍門。這兩個動畫相對簡單,一個是通過opacity來顯示,一個通過translateY來隱藏。最后來看動畫效果。

    圖4 動畫

    3.4 用戶點擊

    用戶點擊狂點按鈕時有兩個交互,一個是狂點按鈕本身會有一個變大變小的過程,其次小魚背后會出現一個光暈,這兩個動畫是每點擊一次才播放一次的。每點擊一次要紀錄一下點擊次數,這個調用抽獎接口的時候要用到,還有要判斷動畫是否已經結束,結束之后點擊是沒有什么效果的,當然這不是這里實現動畫的關鍵。看下面的代碼:

    jump() {
      let tl = anime.timeline()
      if (this.game.finish) return
      this.game.clickCount++
      console.log('this.game.clickCount')
      tl.add({
        targets: '.backdrop',
        duration: 1000,
        keyframes: [
          {opacity: 0.2},
          {opacity: 0.5},
          {opacity: 0.8},
          {opacity: 1.2},
          {opacity: 0.8},
          {opacity: 0.5},
          {opacity: 0.2},
          {opacity: 0},
        ]
      }).add({
        targets: '.btn-click',
        easing: 'linear',
        duration: 200,
        keyframes: [
          {scale: 0.9, opacity: 0.9},
          {scale: 0.8, opacity: 0.8},
          {scale: 0.7, opacity: 0.7},
          {scale: 0.6, opacity: 0.6},
          {scale: 0.8, opacity: 0.5},
          {scale: 0.9, opacity: 0.4},
          {scale: 1, opacity: 0.6},
          {scale: 1.1, opacity: 0.8},
          {scale: 1, opacity: 1}
        ]
      }, 0)
    }
    

    小魚圖片和它背后的光暈都是使用絕對定位,但是小魚的z-index要比光暈大,這樣看起來光暈是在小魚的下方。這兩個動畫都使用了關鍵幀來增強效果。點擊效果圖如下:

    圖5 按鈕點擊

    最后就是調用接口,根據接口彈出中獎結果了,這和動畫無關,只需要傳一個參數,點擊狂點按鈕的次數。最后看一下整體效果,如下圖6:

    圖6 完整動畫

    4.總結

    整個鯉魚跳龍門動畫已經介紹完,這個動畫要考慮的元素很多,有小魚,小魚背后的光暈,龍門,金幣,倒計時,小河等等,整個動畫是由一個一個的小動畫組合而成,只要把要考慮的細節考慮清楚,實現起來還是不難的。

    5.參考

    1. animate https://www.animejs.cn/
    posted @ 2022-06-28 14:37  nd  閱讀(250)  評論(0編輯  收藏  舉報
    国产美女a做受大片观看