1. 程式人生 > >vue之購物車拋物線小球動畫效果實現

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

購物車拋物線小球

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

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

實現步驟:

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

2.原理,購物小球元件在點選加號的時候對外觸發事件,將小球物件本身傳遞給父元件goods元件,再由goods作為橋樑將這個資訊傳遞給另一個子元件shopCart元件,shopCart元件獲取到小球物件後,對該小球進行位置計算,從而實現從不同商品的位置新增商品的拋物線小球效果

3.cartControl.vue部分程式碼

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部分程式碼

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中使用這些方法

可參考以下官網

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'
      }
    },