(十二)小球动画实现

在商品页面,当我们添加商品的时候,即点击“+”按钮,会有一个小球做抛物线的动作降到左下角的购物车里面的动作,现在就讲讲如何实现小球做抛物线的动作。


小球的动作解析:外层做纵轴方向的抛物线动作;内层做水平方向的线性的动作

难点:是在底部的shopCart.vue中获取到小球下落的位置,也就是对应的点击的"+"的位置,然后再shopCart.vue中描写小球下落的动作。

大概思路是:有3个.vue文件,父组件goods.vue,子组件cartControl.vue和子组件shopCart.vue。首先小球的下降的时机(也就是小球下落的位置)是在点击cartControl.vue中的“+”按钮触发的,那么需要将这个触发的对象从cartControl中传到父组件goods.vue中,那么需要在cartControl.vue中的触发“+”的函数中,书写this.$emit(add,event.target),然后在goods.vue中调用cartControl组件的地方绑定该函数,从而让父组件拿到子组件中被点击的对象,如 

在cartControl.vue中的methods属性中定义的函数addCart(event)添加this.$emit('add',event.target)语句,其中add是在父组件goods.vue中的调用子组件cartControl.vue组件地方,通过@add绑定的监听事件属性,event.target是被点击的对象

         //点击food右边的“+”
          addCart(event){
            if( !event._constructed ){
              //不是自己开发的函数,则返回,避免在pc端触发两次
              return
            }
            if( !this.food.count ){
            //  如果this.foo.count的值为undefined,表示food对象中还没有count这个属性
            //   this.food.count = 1;
            //  set(对象,属性,属性值),让手动添加的属性,被浏览器识别监听
              Vue.set(this.food,'count',1);
            }else{
              this.food.count++;
            }
            //添加一个商品时,通过$emit(event,[...args]),将DOM对象传给父组件
            this.$emit('add', event.target);
          }

在goods.vue中调用子组件cart-control的地方绑定监听事件add,其中addFood(target)是在父组件goods.vue中的methods定义的方法

<cart-control  @add="addFood" :food="food"></cart-control>

拿到的点击的对象,就是小球要下落的位置,然后再父组件goods.vue中的methods属性中定义函数addFood(),这个函数主要负责把子组件cartControl.vue中传过来的点击的对象传给父组件,然后父组件在将其点击的对象传给子组件shopCart.vue中定义的函数,这样在shopCart.vue中就可以描写小球下落的动作了

        //监听右侧添加食品的动作事件,把cartControl.vue中点击的对象通过target
        //传到父组件goods.vue文件里面
        addFood(target){
          //然后在goods.vue中拿到子对象,从而可以编写下落的函数
          this._drop(target);
        }

其中,函数_drop(target)也是在父组件goods.vue中的methods属性中定义的函数,他主要负责获取到子组件shopCart.vue子组件对象,然后调用该对象里面定义的方法drop(el),将获取到的target传递给el,其中drop(el)是在shopCart.vue中定义的方法

        //小球下落的动作
        _drop(target){
          //体验优化,异步执行下落动作
          this.$nextTick(()=>{
            //调用 shopcart,给这个组件定义一个小球下落的函数
            //然后在shopCart.vue文件里面定义drop()函数,描写一个小球下落的动作
            //通过this.$refs.shopcart拿到子组件,然后就可以调用子组件里面定义的方法
            this.$refs.shopcart.drop(target);  //调用子组件shopCart.vue中定义的方法
          });
        }

下面看一下shopCart.vue 中的书写

下面这张图是在shopCart.vue中定义小球的html


完整的<template>如下所示

<template>
    <div class="shopCart">
      <div class="content">
        <div class="content-left">
          <div class="logo-wrapper">
            <!--totalCount在computed中定义,购物车里面的数量totalCount大于0,则-->
            <!--购物车背景以及logo呈高亮状态-->
            <div class="logo" :class="{'highlight':totalCount>0}">
              <span class="icon-shopping_cart" :class="{'highlight':totalCount>0}"></span>
            </div>
            <!--logo右上角的数字,当totalCount<=0,则不显示-->
            <div class="num" v-show="totalCount>0">{{totalCount}}</div>
          </div>
          <!--totalPrice在computed里面计算,totalPrice>0则价格总数高亮-->
          <div class="price" :class="{'highlight':totalPrice>0}">¥{{totalPrice}}</div>
          <div class="desc">另需配送费¥{{deliveryPrice}}元</div>
        </div>
        <div class="content-right">
          <!--payClass在computed里面定义,当价格总数>最小价格的时候,最右边的背景呈高亮状态-->
          <div class="pay" :class="payClass">
            {{payDesc}}
          </div>
        </div>
      </div>
      <!--定义小小球-->
      <div class="ball-container">
        <div v-for="ball in balls">
          <transition name="drop" @before-enter="beforeDrop" @enter="dropping" @after-enter="afterDrop">
            <div class="ball" v-show="ball.show">
              <!--.inner是一个小球-->
              <div class="inner inner-hook"></div>
            </div>
          </transition>
        </div>
      </div>
    </div>
</template>

这里用到了vue中的内置组件<transition>,用来描述缓动动画的

在<script>标签的里面的data()需要定义个数组balls表示存放的每一个小球的状态,这里存放的是5个小球的状态,然后还需要定义一个存放下落小球的数组dropBalls,如下所示

      data(){
        return {
          //维护每个小球的状态,因为transition只对v-if  v-show  v-for有过渡效果
          //所以这里定义了show,在<template>中采用v-show指令
          balls:[
            {
              show: false
            },
            {
              show:false
            },
            {
              show:false
            },
            {
              show:false
            },
            {
              show:false
            }
          ],
          dropBalls:[]  //存放下落的小球
        };
      }

然后,需要在methods属性里面定义函数drop(el),主要负责对存放小球状态的数组进行遍历,将show为false的小球的状态改为true,并将它放进dropBalls数组中

      drop(el){
          //对球进行遍历
          let len = this.balls.length;
          for( let i=0;i<len;i++ ){
            let ball = this.balls[i];
            if( !ball.show ){
              ball.show = true;
              ball.el = el;
              this.dropBalls.push(ball); //将下落的小球放进来
              return;
            }
          }
        }

然后利用<transition>组件的事件before-enter、enter、after-enter,去描写小球下落的状态,绑定的事件是在methods中定义的

     <!--定义小小球-->
      <div class="ball-container">
        <div v-for="ball in balls">
          <transition name="drop" @before-enter="beforeDrop" @enter="dropping" @after-enter="afterDrop">
            <div class="ball" v-show="ball.show">
              <!--.inner是一个小球-->
              <div class="inner inner-hook"></div>
            </div>
          </transition>
        </div>
      </div>
       beforeDrop(el){
           //将所有设为true的小球找到
          // 遍历所有的小球
          let count = this.balls.length;
          while(count--){
            let ball = this.balls[count];
            if(ball.show){
              //返回元素的大小及其相对于视口的位置的对象
              let rect = ball.el.getBoundingClientRect();
              //水平和竖直方向的偏移量
              let x = rect.left - 32; //左下角购物车和右侧点击的“+”的水平距离
              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)`;
            }
          }
        },
        dropping(el,done){
          let rf = el.offsetHeight;
          this.$nextTick(()=>{
            //当下降的时候,重写外部和内部的translate3d()
            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)
          });
        },
        afterDrop(el){
          //当降落完一个ball,就将该ball从balls数组中取出来
          let ball = this.dropBalls.shift();
          if(ball){
            //又可以利用该ball
            ball.show = false;
            el.style.display = 'none';
          }
        }
     

shopCart.vue中的<style>中的代码如下

<style lang="stylus">
  .shopCart
    position: fixed
    left:0
    bottom:0
    height:48px
    z-index:50
    width: 100%
    .content
      display:flex
      background: #141d27
      font-size:0
      color:rgba(255,255,255,0.4)
      .content-left
        flex:1
        .logo-wrapper,.price,.desc
          display: inline-block
          vertical-align: top
        .logo-wrapper
          position: relative
          top: -10px
          margin:0 12px
          padding:6px
          width:56px
          height:56px
          box-sizing:border-box
          border-radius: 50%
          background: #141d27
          .logo
            width: 100%
            height: 100%
            border-radius: 50%
            background: #2b343c
            text-align:center
            &.highlight
              background:rgb(0,160,220)
            .icon-shopping_cart
              font-size:24px
              line-height:44px
              color:#80858a
              &.highlight
                color:#fff
          .num
            position: absolute
            top:0
            right:0
            width:24px
            height:16px
            line-height:16px
            text-align:center
            border-radius: 16px
            font-size:9px
            font-weight:700
            color: #ffffff
            background:rgb(240,20,20)
            box-shadow:0 4px 8px 0 rgba(0,0,0,0.4)
        .price,.desc
          line-height:24px
        .price
          margin-top:12px
          padding-right:12px
          box-sizing:border-box
          border-right:1px solid rgba(255,255,255,0.1)
          font-size:16px
          font-weight:700
          &.highlight
            color:#fff
        .desc
          margin:12px 0 0 12px
          font-size:10px
      .content-right
        flex:0 0 105px
        width:105px
        .pay
          height:48px
          line-height:48px
          text-align:center
          font-size:12px
          font-weight:700
          background:#2b333b
          &.not-enough
            background:#2b333b
          &.enough
            background: #00b43c
            color:#fff
    .ball-container
      .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:rgb(0, 160, 220)
          transition:all 0.4s linear
</style>

以上大概就是实现小球做抛物线的动作的过程















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值