vue之購物車拋物線小球動畫效果實現

購物車拋物線小球

先上最終效果圖,在商品頁面和商品詳情頁面點擊加號添加商品時均可以看到小球拋物線落入購物車的動畫效果javascript

此文章只寫了商品頁面購物小球的實現,商品詳情頁原理相似css

實現步驟:html

1.須要三個組件,最下方包含藍色購物車的【購物車】組件shopCart.vue(子組件),每一個【加減號】組成的購物小球組件cartControl.vue(子組件),和包含每一個商品信息的goods組件goods.vue(父組件)vue

2.原理,購物小球組件在點擊加號的時候對外觸發事件,將小球對象自己傳遞給父組件goods組件,再由goods做爲橋樑將這個信息傳遞給另外一個子組件shopCart組件,shopCart組件獲取到小球對象後,對該小球進行位置計算,從而實現從不一樣商品的位置添加商品的拋物線小球效果java

3.cartControl.vue部分代碼web

html代碼數組

<div class="cartControl">
      <transition name="move">
        <!--減小商品-->
        <div class="decrease " v-show="food.count>0" @click.stop.prevent="decreaseCart">
          <span class="inner iconfont">&#xe8c1;</span>
        </div>
      </transition>
      <!--增長商品-->
      <div class="count" v-show="food.count>0">{
  
  
  

 {food.count}}</div>
      <!--點擊加號按鈕,觸發事件addCart,將事件對象做爲參數傳遞-->
      <div class="add iconfont" @click.stop.prevent="addCart($event)">&#xe692;</div>
    </div>

js代碼瀏覽器

// addCart事件
    addCart (event) {
      if (!event._constructed) return // 檢測事件派發是否來自於better-scroll
      if (!this.food.count) {
        // 當給一個觀測對象添加一個它不存在的屬性的時候,直接賦值是不能夠的,須要使用Vue.set設置這個屬性
        Vue.set(this.food, 'count', 1)
      } else {
        this.food.count++
      }
      this.$emit('cart-add', event.target) // 向父組件觸發一個自定義的cart-add事件,同時將事件對象傳遞給父組件
    },

 4.goods.vue部分代碼app

html代碼異步

<!--加減商品-->
<div class="cartControl-wrapper">
   <!--在父組件監聽到子組件觸發的cart-add事件-->
   <cart-control :food="food" @cart-add="handlecartAdd"></cart-control>
</div>

js代碼  知識點:子組件和父組件之間的數據傳遞

_drop (target) { // 在goods.vue定義 _drop方法將cartcontrol的傳遞過來target對象再傳遞給shopCart
      this.$nextTick(() => { // 使用$nextTick優化體驗
        this.$refs.shopCart.drop(target) // 父組件goods經過.$refs屬性訪問shopCart子組件的drop方法
      })
    },
    handlecartAdd (target) { // 點擊加號按鈕觸發事件
      this._drop(target)     // 調用_drop方法
    }

5.shopCart.vue部分代碼

1.定義一個數組,存放5個小球,這5個小球能夠知足的動畫的運行

2.動畫分爲兩層,外層控制小球y軸方向和運動的軌道,內層控制x軸方向的運動

3.使用js動畫鉤子,vue在實現動畫的時候提供了幾個javascript鉤子,可配合css動畫一塊兒使用,也可單獨使用,由於購物車拋物線小球只有進入動畫,沒有離開的動畫,因此enter的鉤子有,before-enter,enter,after-enter,這些鉤子須要在html屬性中聲明,而後在methods中使用這些方法

可參考如下官網

https://cn.vuejs.org/v2/guide/transitions.html#JavaScript-%E9%92%A9%E5%AD%90

4.v-show控制盒子的顯示和隱藏

html

<!--購物車小球-->
      <div class="ball-container">
        <div v-for="(ball,index) of balls" :key="index">
          <transition @before-enter="handleBeforeEnter"
                      @enter="handleEnter"
                      @after-enter="handleAfterEnter">
            <div  class="ball" v-show="ball.show" v-bind:css="false"><!--外層盒子-->
              <div class="inner inner-hook"></div> <!--內層盒子-->
            </div>
          </transition>
        </div>
      </div>

data

data () {
    return { // 使用balls存放5個小球,這些小球的默認狀態都是不顯示的
      balls: [{show: false}, {show: false}, {show: false}, {show: false}, {show: false}],
      dropBalls: [] // 用dropBalls來存放掉落的小球
    }
}

在methods中定義方法

// 當觸發drop方法時小球開始掉落
    drop (el) {
      for (let i = 0; i < this.balls.length; i++) { // 遍歷這5個小球
        let ball = this.balls[i]
        if (!ball.show) {    // 當小球顯示狀態爲隱藏時
          ball.show = true   // 將這個小球的顯示狀態設置爲true
          ball.el = el       // 將cartControl傳過來的對象掛載到ball的el屬性上
          this.dropBalls.push(ball) // 將這個小球放入到dropBalls數組中
          return
        }
      }
    }

js動畫

// js動畫鉤子
    // beforeenter
    handleBeforeEnter: function (el) {
      let count = this.balls.length 
      while (count--) {
        let ball = this.balls[count]
        if (ball.show) {
          let rect = ball.el.getBoundingClientRect() // getBoundingClientRect()獲取小球相對於視窗的位置,屏幕左上角座標爲0,0
          let x = rect.left - 32 // 小球x方向位移= 小球距離屏幕左側的距離-外層盒子距離水平的距離
          let y = -(window.innerHeight - rect.top - 22) // 負數,由於是從左上角向下
          el.style.display = ''
          el.style.webkitTransform = `translate3d(0,${y}px,0)` // 設置外層盒子,即小球垂直方向的位移
          el.style.transform = `translate3d(0,${y}px,0)`
          let inner = el.getElementsByClassName('inner-hook')[0]
          inner.style.webkitTransform = `translate3d(${x}px,0,0)` // 設置內層盒子,即小球水平方向的距離
          inner.style.transform = `translate3d(${x}px,0,0)`
        }
      }
    },
    // enter
    handleEnter: function (el, done) {
      /* eslint-disable no-unused-vars */
      // 觸發瀏覽器重繪
      let rf = el.offsetHeight
      this.$nextTick(() => { // 讓動畫效果異步執行,提升性能
        el.style.webkitTransform = 'translate3d(0, 0, 0)'// 設置小球掉落後最終的位置
        el.style.transform = 'translate3d(0, 0, 0)'
        let inner = el.getElementsByClassName('inner-hook')[0]
        inner.style.webkitTransform = 'translate3d(0, 0, 0)'
        inner.style.transform = 'translate3d(0, 0, 0)'
        el.addEventListener('transitionend', done) // Vue爲了知道過渡的完成,必須設置相應的事件監聽器。它能夠是transitionend或 animationend
      })
    },
    handleAfterEnter: function (el) {
      let ball = this.dropBalls.shift() // 完成一次動畫就刪除一個dropBalls的小球
      if (ball) {
        ball.show = false
        el.style.display = 'none'
      }
    },