溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Vue動(dòng)畫實(shí)例代碼分析

發(fā)布時(shí)間:2022-09-14 10:08:46 來(lái)源:億速云 閱讀:146 作者:iii 欄目:編程語(yǔ)言

這篇“Vue動(dòng)畫實(shí)例代碼分析”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來(lái)看看這篇“Vue動(dòng)畫實(shí)例代碼分析”文章吧。

Vue 動(dòng)畫

CSS 過(guò)渡

以顯示和隱藏動(dòng)畫為例:

<div id="demo">
  <button v-on:click="show = !show">
    Toggle
  </button>
  <transition name="fade">
    <p v-if="show">hello</p>
  </transition>
</div>
從隱藏到顯示

當(dāng)動(dòng)畫開始的時(shí)候,P 標(biāo)簽還是隱藏的,此時(shí) Vue 會(huì)給 P 加上兩個(gè)class:

.fade-enter {
   opacity: 0;
}
.fade-enter-active {
   transition: opacity 0.5s;
}

Vue動(dòng)畫實(shí)例代碼分析

當(dāng)動(dòng)畫開始后,會(huì)移除.fade-enter(在元素被插入之前生效,在元素被插入之后的下一幀移除),這個(gè)時(shí)候 P 標(biāo)簽的 opacity就恢復(fù)到 1,即顯示,這個(gè)時(shí)候就會(huì)觸發(fā)transition , 檢測(cè)到opacity的變化,就會(huì)產(chǎn)生動(dòng)畫。當(dāng)動(dòng)畫結(jié)束后,會(huì)移除 Vue 加上的 class(v-enter-to, v-enter-active)。

Vue動(dòng)畫實(shí)例代碼分析

上面這個(gè)過(guò)程是怎么實(shí)現(xiàn)的呢?它主要用到了requestAnimationFrame這個(gè)api,我們自己可以實(shí)現(xiàn)一個(gè)簡(jiǎn)易版的動(dòng)畫,當(dāng)生成下一幀的時(shí)候添加或刪除某個(gè)類,從而形成動(dòng)畫效果。

<!DOCTYPE html>
<html>
  <head>
    <title>Document</title>
    <style type="text/css">
      .box {
        width: 100px;
        height: 100px;
        background-color: red;
      }
      .enter {
        opacity: 0;
      }
      .mov {
        transition: opacity 5s linear;
      }
    </style>
  </head>
  <body>
    <div id="box" class="box mov enter"></div>
    <script>
      var box = document.getElementById('box')
      // 第一幀之后執(zhí)行
      requestAnimationFrame(function() {
        box.setAttribute('class', 'box mov')
      })
    </script>
  </body>
</html>

當(dāng)生成下一幀的時(shí)候,會(huì)移除enter這個(gè)class,那么 div 就會(huì)顯示出來(lái),就會(huì)觸發(fā)transition產(chǎn)生動(dòng)畫效果。

從顯示到隱藏

當(dāng)隱藏的時(shí)候也產(chǎn)生動(dòng)畫,如下圖:

Vue動(dòng)畫實(shí)例代碼分析

.fade-leave-to {
   opacity: 0;
}
.fade-leave-active {
   transition: opacity 0.5s;
}

剛開始 P 標(biāo)簽是顯示的,因?yàn)榇藭r(shí)fade-leave (在離開過(guò)渡被觸發(fā)時(shí)立刻生效,下一幀被移除) 的樣式是 opacity 是 1,執(zhí)行到第二幀的時(shí)候加上fade-leave-to(在離開過(guò)渡被觸發(fā)之后下一幀生效 ,與此同時(shí) fade-leave 被刪除),此時(shí)opacity 是 0,既然發(fā)生了屬性的變化,transition就會(huì)監(jiān)聽(tīng)到,從而形成動(dòng)畫。

這樣顯示和隱藏就形成了一個(gè)完整的動(dòng)畫。

原理是當(dāng)你通過(guò)點(diǎn)擊事件改變css屬性,比如opacity時(shí),transition會(huì)檢測(cè)到這個(gè)變化,從而形成動(dòng)畫。

CSS 動(dòng)畫

CSS 動(dòng)畫原理同 CSS 過(guò)渡類似,區(qū)別是在動(dòng)畫中 v-enter 類名在節(jié)點(diǎn)插入 DOM 后不會(huì)立即刪除,而是在 animationend 事件觸發(fā)時(shí)刪除。

<style>
  .bounce-enter-active {
    animation: bounce-in 3s;
  }
  .bounce-leave-active {
    animation: bounce-in 3s reverse;
  }
  @keyframes bounce-in {
    0% {
      transform: scale(0);
    }
    50% {
      transform: scale(1.5);
    }
    100% {
      transform: scale(1);
    }
  }
</style>

<div id="demo">
  <button @click="show = !show">Toggle show</button>
  <transition name="bounce">
    <p v-if="show">
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris
      facilisis enim libero, at lacinia diam fermentum id. Pellentesque
      habitant morbi tristique senectus et netus.
    </p>
  </transition>
</div>

當(dāng)我們點(diǎn)擊改變showfalse時(shí),會(huì)在 P 標(biāo)簽上添加.bounce-leave-active這個(gè)class,這個(gè)類就會(huì)執(zhí)行animation 動(dòng)畫,當(dāng)動(dòng)畫執(zhí)行完成后刪除.bounce-leave-active。

JavaScript 鉤子動(dòng)畫

<!DOCTYPE html>
<html>
  <head>
    ...
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
  </head>
  <body>
    <div id="demo">
      <button @click="show = !show">
        Toggle
      </button>
      <transition
        v-on:before-enter="beforeEnter"
        v-on:enter="enter"
        v-on:leave="leave"
        v-bind:css="false"
      >
        <p v-if="show">
          Demo
        </p>
      </transition>
    </div>

    <script>
      new Vue({
        el: '#demo',
        data: {
          show: true
        },
        methods: {
          beforeEnter: function(el) {
            el.style.opacity = 0
            el.style.transformOrigin = 'left'
          },
          enter: function(el, done) {
            Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 1000 })
            Velocity(el, { fontSize: '1em' }, { complete: done })
          },
          leave: function(el, done) {
            Velocity(
              el,
              { translateX: '15px', rotateZ: '50deg' },
              { duration: 600 }
            )
            Velocity(
              el,
              {
                rotateZ: '45deg',
                translateY: '30px',
                translateX: '30px',
                opacity: 0
              },
              { complete: done }
            )
          }
        }
      })
    </script>
  </body>
</html>
  • before-enter: 是指在動(dòng)畫之前執(zhí)行的函數(shù);

  • enter: 就是整個(gè)動(dòng)畫的過(guò)程, 執(zhí)行完后要加一個(gè)done(),來(lái)告訴vue已經(jīng)執(zhí)行完畢;當(dāng)只用 JavaScript 過(guò)渡的時(shí)候,在 enter 和 leave 中必須使用 done 進(jìn)行回調(diào)。否則,它們將被同步調(diào)用,過(guò)渡會(huì)立即完成。

  • after-enter: 動(dòng)畫結(jié)束之后執(zhí)行;

Javascript 鉤子動(dòng)畫一般用在比較復(fù)雜的動(dòng)畫上,而不是簡(jiǎn)單的過(guò)渡動(dòng)畫。后面我們會(huì)用很大的篇幅通過(guò)幾個(gè)例子來(lái)說(shuō)明用 Javascript 鉤子動(dòng)畫是如何完成復(fù)雜動(dòng)畫的。

初始渲染的過(guò)渡

因?yàn)镃SS過(guò)渡動(dòng)畫需要有個(gè)觸發(fā)條件,比如opacity必須有一個(gè)變化,如果沒(méi)有變化就不會(huì)觸發(fā)。那么,可以通過(guò) appear attribute 設(shè)置節(jié)點(diǎn)在初始渲染的過(guò)渡:

<transition appear>
    <!-- ... -->
</transition>

CSS 動(dòng)畫(animation)則不需要觸發(fā)條件。

多個(gè)元素的過(guò)渡

一旦涉及到多個(gè)元素的過(guò)渡,那么就會(huì)出現(xiàn)舊元素和新元素進(jìn)出的先后問(wèn)題。<transition> 的默認(rèn)行為是進(jìn)入和離開同時(shí)發(fā)生,但是這樣就會(huì)產(chǎn)生一些不協(xié)調(diào)的效果,所以 Vue 提供了過(guò)渡模式

  • in-out:新元素先進(jìn)行過(guò)渡,完成之后當(dāng)前元素過(guò)渡離開(in-out 模式不是經(jīng)常用到,但對(duì)于一些稍微不同的過(guò)渡效果還是有用的)。

  • out-in:當(dāng)前元素先進(jìn)行過(guò)渡,完成之后新元素過(guò)渡進(jìn)入(這個(gè)用的比較多)。

<transition name="fade" mode="out-in">
    <!-- ... the buttons ... -->
</transition>

但是這兩個(gè)模式并不能完全滿足實(shí)際需要,實(shí)際上我們可以定制我們要想的先后效果,比如后臺(tái)管理系統(tǒng)中有一個(gè)面包屑導(dǎo)航欄,當(dāng)改變路由的時(shí)候需要更改面包屑里面的內(nèi)容,那么這個(gè)更改的動(dòng)畫可以讓舊的元素向左滑出,新的元素從右邊滑入。

<div id="demo">
  <div>
    <button @click="show = !show">
      Toggle
    </button>
  </div>

  <transition name="fade">
    // 一定要設(shè)置key
    <div class="cls" v-if="show" key="1">
      if Demo
    </div>
    <div class="cls" v-else key="2">else demo</div>
  </transition>
</div>

<style>
  .cls {
    display: inline-block;
  }
  .fade-enter-active,
  .fade-leave-active {
    transition: all 1s;
    // 這個(gè)定位設(shè)置很關(guān)鍵
    position: absolute;
  }

  .fade-enter {
    opacity: 0;
    transform: translateX(30px);
  }

  .fade-leave-to {
    opacity: 0;
    transform: translateX(-30px);
  }
</style>

當(dāng)有相同標(biāo)簽名的元素切換時(shí),需要通過(guò) key attribute 設(shè)置唯一的值來(lái)標(biāo)記以讓 Vue 區(qū)分它們,否則 Vue 為了效率只會(huì)替換相同標(biāo)簽內(nèi)部的內(nèi)容。即使在技術(shù)上沒(méi)有必要,給在 transition 組件中的多個(gè)元素設(shè)置 key 是一個(gè)更好的實(shí)踐。

多個(gè)組件的過(guò)渡

多個(gè)組件的過(guò)渡簡(jiǎn)單很多 - 我們不需要使用 key attribute。相反,我們只需要使用動(dòng)態(tài)組件:

<transition name="component-fade" mode="out-in">
    <component v-bind:is="view"></component>
</transition>

new Vue({
  el: '#transition-components-demo',
  data: {
    view: 'v-a'
  },
  components: {
    'v-a': {
      template: '<div>Component A</div>'
    },
    'v-b': {
      template: '<div>Component B</div>'
    }
  }
})

.component-fade-enter-active, .component-fade-leave-active {
  transition: opacity .3s ease;
}
.component-fade-enter, .component-fade-leave-to {
  opacity: 0;
}

列表過(guò)渡

上面講的動(dòng)畫都是針對(duì)單個(gè)節(jié)點(diǎn),或者同一時(shí)間渲染多個(gè)節(jié)點(diǎn)中的一個(gè),那么怎么同時(shí)渲染整個(gè)列表,比如使用 v-for?

在這種場(chǎng)景中,使用 <transition-group> 組件,這個(gè)組件的幾個(gè)特點(diǎn):

  • 不同于 <transition>,它會(huì)以一個(gè)真實(shí)元素呈現(xiàn):默認(rèn)為一個(gè) <span>。你也可以通過(guò) tag attribute 更換為其他元素。

  • 過(guò)渡模式不可用,因?yàn)槲覀儾辉傧嗷デ袚Q特有的元素。

  • 內(nèi)部元素總是需要提供唯一的 key attribute 值。

  • CSS 過(guò)渡的類將會(huì)應(yīng)用在內(nèi)部的元素中,而不是這個(gè)組/容器本身。

后面我們會(huì)通過(guò)一個(gè)例子演示如何使用<transition-group> 。

案例

路由動(dòng)畫

在后臺(tái)管理系統(tǒng)中,當(dāng)路由變化時(shí),對(duì)應(yīng)的組件內(nèi)容也會(huì)發(fā)生變化,當(dāng)在變化時(shí)加上一個(gè)動(dòng)畫,讓整個(gè)頁(yè)面效果更加自然。

<transition name="fade-transform" mode="out-in">
  // 這里加了key
  <router-view :key="key">
</transition>

computed: {
  key() {
      return this.$route.path
  }
}

.fade-transform-leave-active,
.fade-transform-enter-active {
    transition: all 0.5s;
}

.fade-transform-enter {
    opacity: 0;
    transform: translateX(-30px)
}

.fade-transform-leave-to {
    opacity: 0;
    transform: translateX(30px)
}

H5 頁(yè)面彈框從底部彈出動(dòng)畫

在 H5 頁(yè)面開發(fā)中,一個(gè)常用的功能是點(diǎn)擊一個(gè)按鈕,隱藏的內(nèi)容從屏幕底部彈出,同時(shí)彈出的時(shí)候有個(gè)動(dòng)畫效果,一般是緩慢上升。

<div id="list-demo">
  <transition name="list-fade">
    // 外面一層遮罩
    <div class="playlist" v-show="showFlag" @click="hide">
      // 這里面才是內(nèi)容
      <div class="list-wrapper"></div>
    </div>
  </transition>

  <div @click="show" class="add">
    Add
  </div>
</div>

<style>
  .add {
    position: fixed;
    bottom: 0;
    left: 0;
    text-align: center;
    line-height: 40px;
  }

  .playlist {
    position: fixed;
    z-index: 200;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background-color: rgba(0, 0, 0, 0.3);
  }
  .list-wrapper {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 400px;
    background-color: #333;
  }
  
  // 針對(duì)最外面一層的遮罩
  .list-fade-enter-active,
  .list-fade-leave-active {
    transition: opacity 0.3s;
  }
  // 針對(duì)類為list-wrapper的內(nèi)容
  .list-fade-enter-active .list-wrapper,
  .list-fade-leave-active .list-wrapper {
    transition: all 0.3s;
  }
  
  .list-fade-enter,
  .list-fade-leave-to {
    opacity: 0;
  }
  
  // 最開始內(nèi)容是隱藏的,所以translate3d(0, 100%, 0)
  .list-fade-enter .list-wrapper,
  .list-fade-leave-to .list-wrapper {
    transform: translate3d(0, 100%, 0);
  }
</style>

這個(gè)動(dòng)畫有兩層,一層是最外層的內(nèi)容,另一層是最里面部分的內(nèi)容

列表刪除動(dòng)畫

在 H5 頁(yè)面開發(fā)中,如果在一個(gè)列表頁(yè)中刪除其中一個(gè)子項(xiàng),要求有一個(gè)刪除動(dòng)畫效果。

<div id="list-demo">
  <transition-group ref="list" name="list" tag="ul">
    <li :key="item.id" class="item" v-for="(item, index) in arr">
      <span class="text" v-html="item.name"></span>
      <span class="delete" @click="deleteOne(item, index)">
        delete
      </span>
    </li>
  </transition-group>
</div>

.item {
  height: 40px;
}
.list-enter-active,
.list-leave-active {
  transition: all 0.1s
}
.list-enter,
.list-leave-to {
  height: 0
}

復(fù)雜動(dòng)畫-案例1

一般復(fù)雜的動(dòng)畫,并不能用簡(jiǎn)單的 css 過(guò)渡或者 css 動(dòng)畫能實(shí)現(xiàn)的,需要使用 javascript鉤子動(dòng)畫實(shí)現(xiàn)。

針對(duì)上面圖片的動(dòng)畫進(jìn)行拆解:

  • 當(dāng)從底部談起的時(shí)候,頁(yè)面頂部(向下的箭頭和歌曲名稱部分)從上往下滑入,同時(shí)有個(gè)回彈的效果,同時(shí),底部(播放進(jìn)度條的部分)從下往上滑入,也有一個(gè)回彈的效果。

  • 左下角旋轉(zhuǎn)的圓有兩個(gè)動(dòng)畫效果,一個(gè)是從小變大,一個(gè)是從左下角滑動(dòng)到中心部分

  • 圓的旋轉(zhuǎn)動(dòng)畫

代碼結(jié)構(gòu):

<div>
    <transition
      name="normal"
      @enter="enter"
      @after-enter="afterEnter"
      @leave="leave"
      @after-leave="afterLeave"
    >
      <div class="normal-player" v-show="fullScreen">
        <div class="top">
          // 頂部區(qū)域...
        </div>
        <div
          class="middle"
          @touchstart.prevent="middleTouchStart"
          @touchmove.prevent="middleTouchMove"
          @touchend.prevent="middleTouchEnd"
        >
          // 中間區(qū)域...   
        </div>
        <div class="bottom">
          // 底部區(qū)域...
        </div>
      </div>
    </transition>
    <transition name="mini">
      <div class="mini-player" v-show="!fullScreen" @click="open">
         // 內(nèi)容區(qū)域...  
      </div>
    </transition>
</div>

實(shí)現(xiàn)第一個(gè)動(dòng)畫效果

// 這是stylus的寫法
.normal-enter-active,
.normal-leave-active
  transition: all 0.4s
  .top,
  .bottom
    // 通過(guò)這個(gè)白塞爾曲線,使得動(dòng)畫有個(gè)回彈的效果
    transition: all 0.4s cubic-bezier(0.86, 0.18, 0.82, 1.32)
.normal-enter,
.normal-leave-to
  opacity: 0
  .top
    // 從上往下滑入
    transform: translate3d(0, -100px, 0)
  .bottom
    // 從下往上滑入
    transform: translate3d(0, 100px, 0)

通過(guò)第一章節(jié)部分的學(xué)習(xí),看懂這段動(dòng)畫代碼應(yīng)該不難。

實(shí)現(xiàn)第二個(gè)動(dòng)畫效果

要實(shí)現(xiàn)這個(gè)動(dòng)畫效果,必須要計(jì)算左下角的圓到中心部分的圓的 x 軸和 y 軸方向上的距離,因?yàn)镠5頁(yè)面在不同的手機(jī)屏幕下,這個(gè)距離是不同的,所以一開始就不能寫死,只能通過(guò) javascript 去動(dòng)態(tài)的獲取。

// 計(jì)算從小圓中心到大圓中心的距離以及縮放比例
_getPosAndScale() {
  const targetWidth = 40
  const paddingLeft = 40
  const paddingBottom = 30
  const paddingTop = 80
  const width = window.innerWidth * 0.8
  const scale = targetWidth / width
  const x = -(window.innerWidth / 2 - paddingLeft)
  const y = window.innerHeight - paddingTop - width / 2 - paddingBottom
  return {
    x,
    y,
    scale
  }
}

這段代碼細(xì)節(jié)可以不用看,只需要知道它是計(jì)算左下角小圓的原心到中心部分圓的原心的距離(x, y),以及根據(jù)圓的直徑獲取放大縮小倍數(shù)(scale)。

// 這個(gè)庫(kù)可以讓我們使用js來(lái)創(chuàng)建一個(gè)keyframe的動(dòng)畫,為什么要用js來(lái)生成呢?這是因?yàn)橛行┳兓膶傩孕枰獎(jiǎng)討B(tài)的計(jì)算,而不是一開始就定好了
import animations from 'create-keyframe-animation'


// 動(dòng)畫鉤子
// done:當(dāng)動(dòng)畫執(zhí)行完后執(zhí)行done函數(shù),然后跳到afterEnter鉤子函數(shù)
enter(el, done) {
  const { x, y, scale } = this._getPosAndScale()
  // 對(duì)于大圓來(lái)說(shuō),進(jìn)入的時(shí)機(jī)就是從小圓到小圓
  let animation = {
    0: {
      // 一開始大圓相對(duì)于小圓的位置,所以x為負(fù)數(shù),y為整數(shù)
      transform: `translate3d(${x}px, ${y}px, 0) scale(${scale})`
    },
    // scale: 1.1 這樣圓就有個(gè)放大后變回原樣的效果
    60: {
      transform: 'translate3d(0, 0, 0) scale(1.1)'
    },
    100: {
      transform: 'translate3d(0, 0, 0) scale(1)'
    }
  }
  // 設(shè)置animation
  animations.registerAnimation({
    name: 'move',
    animation,
    presets: {
      duration: 400,
      easing: 'linear'
    }
  })
  // 往dom上加上這個(gè)animation,并執(zhí)行動(dòng)畫
  animations.runAnimation(this.$refs.cdWrapper, 'move', done)
},
// 動(dòng)畫結(jié)束之后把樣式置為空
afterEnter() {
  animations.unregisterAnimation('move')
  this.$refs.cdWrapper.style.animation = ''
},
leave(el, done) {
  this.$refs.cdWrapper.style.transition = 'all 0.4s'
  const { x, y, scale } = this._getPosAndScale()
  this.$refs.cdWrapper.style[
    transform
  ] = `translate3d(${x}px,${y}px,0) scale(${scale})`
  // 這樣寫的目的是如果沒(méi)有監(jiān)聽(tīng)到動(dòng)畫結(jié)束的事件,那么我們自己就寫一個(gè)定時(shí)器,400ms后執(zhí)行done函數(shù)
  const timer = setTimeout(done, 400)
  // 監(jiān)聽(tīng)動(dòng)畫結(jié)束
  this.$refs.cdWrapper.addEventListener('transitionend', () => {
    clearTimeout(timer)
    done()
  })
}

這段代碼的效果就是大圓的動(dòng)畫效果。當(dāng)點(diǎn)擊小圓的時(shí)候,大圓開始進(jìn)入,進(jìn)入的過(guò)程就是動(dòng)畫的過(guò)程。當(dāng)點(diǎn)擊向下的箭頭,大圓將消失,消失的過(guò)程就是大圓退出的動(dòng)畫過(guò)程。

雖然有點(diǎn)復(fù)雜,但是也不難看懂,以后我們對(duì)于復(fù)雜動(dòng)畫可以模仿上面的代碼。

那小圓的動(dòng)畫呢?它非常簡(jiǎn)單,就是一個(gè)顯示隱藏的動(dòng)畫:

.mini-enter-active,
.mini-leave-active
  transition: all 0.4s
.mini-enter,
.mini-leave-to
  opacity: 0

至此,就完成小圓和大圓的聯(lián)動(dòng)動(dòng)畫,整體效果還是很驚艷的。

實(shí)現(xiàn)第三個(gè)動(dòng)畫

// 模板部分
<div class="cd" ref="imageWrapper">
  <img
    ref="image"
    :class="cdCls"
    class="image"
    :src="currentSong.image"
  />
</div>

// 邏輯部分
// 通過(guò)事件來(lái)控制playing的值,然后改變img標(biāo)簽的class,從而是動(dòng)畫停止和展示
cdCls() {
   return this.playing ? 'play' : 'play pause'
}

// css部分
.play
  animation: rotate 20s linear infinite
.pause
  animation-play-state: paused
  
@keyframes rotate
  0%
    transform: rotate(0)
  100%
    transform: rotate(360deg)

復(fù)雜動(dòng)畫-案例2

首先對(duì)動(dòng)畫進(jìn)行拆解:

  • 當(dāng)點(diǎn)擊 + 的時(shí)候,有一個(gè)小圓從右側(cè)向xiang左側(cè)滾動(dòng)出來(lái)到指定的位置,既有滾動(dòng)的效果,也有從右往左移動(dòng)的效果。同時(shí),當(dāng)點(diǎn)擊 - 的時(shí)候,小圓從左側(cè)滾動(dòng)到右側(cè)并消失。

  • 當(dāng)點(diǎn)擊 + 的時(shí)候,會(huì)出現(xiàn)一個(gè)小球,這個(gè)小球會(huì)從點(diǎn)擊的位置做一個(gè)拋物線運(yùn)動(dòng)軌跡到左下角的購(gòu)物車中。同時(shí),當(dāng)我連續(xù)點(diǎn)擊的時(shí)候,會(huì)出現(xiàn)多個(gè)小球同時(shí)做拋物線運(yùn)行出現(xiàn)在屏幕中。

實(shí)現(xiàn)第一個(gè)動(dòng)畫

通過(guò)上面的學(xué)習(xí),對(duì)這一個(gè)動(dòng)畫的實(shí)現(xiàn)應(yīng)該不難。代碼如下:

<div class="cartcontrol">
  // 小圓 - 
  <transition name="move">
    <div class="cart-decrease" v-show="food.count>0" @click.stop="decrease">
      <span class="inner"> - </span>
    </div>
  </transition>
  <div class="cart-count" v-show="food.count>0">{{food.count}}</div>
  // 小圓 + 
  <div class="cart-add" @click.stop="add"> + </div>
</div>

.move-enter-active, &.move-leave-active
  transition: all 0.4s linear
.move-enter, &.move-leave-active
  // 外層動(dòng)畫是從右往左運(yùn)動(dòng)
  opacity: 0
  transform: translate3d(24px, 0, 0)
  .inner
    // 內(nèi)層動(dòng)畫是旋轉(zhuǎn)180°
    transform: rotate(180deg)

實(shí)現(xiàn)第二個(gè)動(dòng)畫

  • 創(chuàng)建小球,因?yàn)檫@個(gè)動(dòng)畫就是對(duì)小球的動(dòng)畫

<div class="ball-container">
  <div v-for="(ball,index) in balls" :key="index">
    <transition
      @before-enter="beforeDrop"
      @enter="dropping"
      @after-enter="afterDrop">
      <div class="ball" v-show="ball.show">
        <div class="inner inner-hook"></div>
      </div>
    </transition>
  </div>
</div>

<script>
function createBalls() {
  let balls = []
  for (let i = 0; i < BALL_LEN; i++) {
    balls.push({ show: false })
  }
  return balls
}

data() {
  return {
    balls: createBalls()
  }
},  
</script>

這里創(chuàng)建是10個(gè)小球,小球開始的狀態(tài)都是隱藏的。

  • 點(diǎn)擊 + 按鈕的時(shí)候,觸發(fā)一個(gè)小球彈出

// 點(diǎn)擊加號(hào)調(diào)用這個(gè)函數(shù),同時(shí)把加號(hào)的dom傳遞,這樣就能知道小球運(yùn)動(dòng)的起點(diǎn)位置
onAdd(target) {
  // shopCart就是圖中底部組件,執(zhí)行drop函數(shù)
  this.$refs.shopCart.drop(target)
},

// 把加號(hào)對(duì)應(yīng)的dom傳入,并綁定到小球el屬性上
drop(el) {
  for (let i = 0; i < this.balls.length; i++) {
    const ball = this.balls[i]
    if (!ball.show) {
      ball.show = true
      ball.el = el
      // dropBalls表示正在下落的小球,因?yàn)楫?dāng)快速點(diǎn)擊時(shí),會(huì)觸發(fā)多個(gè)小球下落
      this.dropBalls.push(ball)
      return
    }
  }
},

因?yàn)樾∏虻?code>ball.show為true,那么就會(huì)觸發(fā)對(duì)應(yīng)的動(dòng)畫鉤子函數(shù),首先觸發(fā)beforeDrop:

beforeDrop(el) {
  // 取出最后一個(gè)小球
  const ball = this.dropBalls[this.dropBalls.length - 1]
  // 獲取小球的起點(diǎn)位置,就是在哪個(gè)地方點(diǎn)擊的加號(hào)按鈕
  const rect = ball.el.getBoundingClientRect()
  const x = rect.left - 32
  const y = -(window.innerHeight - rect.top - 22)
  // 設(shè)置小球的位置,把小球設(shè)置到點(diǎn)擊加號(hào)按鈕的那個(gè)地方
  el.style.display = ''
  // 外層動(dòng)畫,向下
  el.style.transform = el.style.webkitTransform = `translate3d(0,${y}px,0)`
  const inner = el.getElementsByClassName(innerClsHook)[0]
  // 內(nèi)層動(dòng)畫向左
  inner.style.transform = inner.style.webkitTransform = `translate3d(${x}px,0,0)`
}

接著執(zhí)行enter事件函數(shù)dropping:

dropping(el, done) {
  // 觸發(fā)瀏覽器重繪,把beforeDrop事件中設(shè)置的小球位置從底部位置移動(dòng)到點(diǎn)擊加號(hào)的位置,這樣小球就會(huì)從上面往下面落下
  this._reflow = document.body.offsetHeight
  // 設(shè)置小球落下的終點(diǎn)位置
  el.style.transform = el.style.webkitTransform = `translate3d(0,0,0)`
  const inner = el.getElementsByClassName(innerClsHook)[0]
  inner.style.transform = inner.style.webkitTransform = `translate3d(0,0,0)`
  // 監(jiān)聽(tīng)動(dòng)畫結(jié)束
  el.addEventListener('transitionend', done)
}

最后執(zhí)行after-enter事件函數(shù)afterDrop:

afterDrop(el) {
  // 取出第一個(gè)小球,設(shè)置屬性show為false,同時(shí)要設(shè)置el.style.display = 'none'
  const ball = this.dropBalls.shift()
  if (ball) {
    ball.show = false
    el.style.display = 'none'
  }
}

我們來(lái)梳理下流程:

  • 點(diǎn)擊加號(hào)位置,觸發(fā)drop函數(shù),然后把一個(gè)隱藏的小球設(shè)置為顯示狀態(tài),存儲(chǔ)在dropBalls中,因?yàn)橛脩艨梢钥焖冱c(diǎn)擊,所以dropBalls里面可能有多個(gè)小球。

  • 當(dāng)把小球狀態(tài)設(shè)置為顯示狀態(tài),就會(huì)觸發(fā)動(dòng)畫鉤子before-enter,enter,after-enter這三個(gè)鉤子。

  • before-enter的作用是把小球的位置設(shè)置到點(diǎn)擊的位置,同時(shí)利用offsetHeight觸發(fā)瀏覽器重繪,這樣就會(huì)把小球的位置放在點(diǎn)擊的位置。

  • enter的作用就是讓小球從點(diǎn)擊的位置落下,動(dòng)畫分為兩層,一層是向下,另一層是向左,當(dāng)兩者結(jié)合就構(gòu)成了一個(gè)從右上角到左下角斜線的動(dòng)畫軌跡,但是一個(gè)斜的直線動(dòng)畫軌跡比較丑,這里就使用了時(shí)間函數(shù)cubic-bezier來(lái)改變動(dòng)畫軌跡,使其有一個(gè)先向上運(yùn)動(dòng),最后向下運(yùn)動(dòng)的拋物線軌跡動(dòng)畫。

.ball
  position: fixed
  left: 32px
  bottom: 22px
  z-index: 200
  transition: all 0.4s cubic-bezier(0.49, -0.29, 0.75, 0.41)
  .inner
    width: 16px
    height: 16px
    border-radius: 50%
    background: $color-blue
    transition: all 0.4s linear
  • after-enter當(dāng)小球到達(dá)目的后,需要把小球隱藏起來(lái),所以取出第一個(gè)小球,然后設(shè)置show的屬性為false,這樣小球就隱藏起來(lái),等待下一次動(dòng)畫執(zhí)行。

所以函數(shù)的執(zhí)行順序是:drop -> before-enter -> enter -> after-enter -> drop -> before-enter -> enter -> after-enter...,這樣就形成了在頁(yè)面中同時(shí)出現(xiàn)多個(gè)小球的動(dòng)畫。

注意:當(dāng)我們?cè)O(shè)置show的屬性為false就可以了,但是代碼中同時(shí)也設(shè)置了el.style.display = 'none',如果不設(shè)置這個(gè)小球消失有一個(gè)延遲。

以上就是關(guān)于“Vue動(dòng)畫實(shí)例代碼分析”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

vue
AI