小程序实现飞入购物车动画

3,759 阅读3分钟

1.应用场景 电商类的小程序

需求:在首页和分类页中,点击加入购物的小按钮之后,该商品的图片曲线运动至底部TabBar的购物车按钮中,运动时图片也在缩小

2.遇到的问题

1.曲线运动的路径
2.图片位置和点击区域的位置不一致
3.实现缩小动画

3.解决方案

1.用贝塞尔曲线获取运动的轨迹点,通过动画+setInterval来实现运动
在app.js中加入贝塞尔曲线计算方法
   // 在app.js中加入贝塞尔曲线计算方法
  /**
   * 贝塞尔曲线
   */
  bezier: function(points, part) {
    let sx = points[0]['x'];
    let sy = points[0]['y'];
    let cx = points[1]['x'];
    let cy = points[1]['y'];
    let ex = points[2]['x'];
    let ey = points[2]['y'];
    var bezier_points = [];
    // 起始点到控制点的x和y每次的增量
    var changeX1 = (cx - sx) / part;
    var changeY1 = (cy - sy) / part;
    // 控制点到结束点的x和y每次的增量
    var changeX2 = (ex - cx) / part;
    var changeY2 = (ey - cy) / part;
    //循环计算
    for (var i = 0; i <= part; i++) {
      // 计算两个动点的坐标
      var qx1 = sx + changeX1 * i;
      var qy1 = sy + changeY1 * i;
      var qx2 = cx + changeX2 * i;
      var qy2 = cy + changeY2 * i;
      // 计算得到此时的一个贝塞尔曲线上的点
      var lastX = qx1 + ((qx2 - qx1) * i) / part;
      var lastY = qy1 + ((qy2 - qy1) * i) / part;
      // 保存点坐标
      var point = {};
      point['x'] = lastX;
      point['y'] = lastY;
      bezier_points.push(point);
    }
    //console.log(bezier_points)
    return {
      'bezier_points': bezier_points
    };
  },

在组件中 js

// component/flyToCart/flyToCart.js
const app = getApp()
Component({
  lifetimes: {
    attached: function () {
      // 获取屏幕宽度,以及TabBar数量,算出终点位置
      this.endPos={};
      this.endPos['x'] = (app.globalData.clientWidth / 10) * 6;
      this.endPos['y'] = app.globalData.clientHeight;
    },
    detached: function () {
      // 在组件实例被从页面节点树移除时执行
    },
  },
  /**
   * 组件的初始数据
   */
  data: {
    ani:null,
    flag:true,//防止连续点击
  },

  /**
   * 组件的方法列表
   */
  methods: {
    show(data) {
      app.debugInfo('传入值',data)
      if (!this.data.flag) return;
      this.setData({
        flag:false
      })
      let {
        startPos,//初始位置
        img//图片路径
      }=data
      this.setData({
        imgUrl:img,
        startPos
      })
      var topPoint = {};
      // 计算最高点y坐标
      if (startPos['y'] <= this.endPos['y']) {
        topPoint['y'] = startPos['y'] - 150;
      } else {
        topPoint['y'] = this.endPos['y'] - 150;
      }
      // 计算最高点x坐标
      if(startPos['x'] <= this.endPos['x']){
        topPoint['x'] = (this.endPos['x'] - startPos['x']) / 2 + startPos['x']
      }else {
        topPoint['x'] = (startPos['x'] - this.endPos['x']) / 2 + this.endPos['x']
      }
      this.linePos = app.bezier([startPos, topPoint, this.endPos], 30);
      // 缩小动画
      this.scaleAnimation()
      // 贝塞尔曲线动画
      this.startAnimation();
    },
    startAnimation: function() {
      var index = 0,
        that = this,
        bezier_points = that.linePos['bezier_points'];
      this.setData({
        bus_x: that.data.startPos.x,
        bus_y: that.data.startPos.y
      })
      this.timer = setInterval(function() {
        index++;
        that.setData({
          bus_x: bezier_points[index]['x'],
          bus_y: bezier_points[index]['y']
        })
        if (index >= 28) {
          clearInterval(that.timer);
          app.debugInfo('time',index*33)
          that.setData({
            flag:true,
          })
          // 重置动画
          that.scaleAnimation('originl')
        }
      }, 33);
    },
    scaleAnimation:function(type){
      var animation = wx.createAnimation({
        duration: 924, // 33*28
        timingFunction: 'linear'
      })
      let that = this;
      if (type =='originl'){
        animation.scale(1).step();
        this.setData({
          ani: animation.export()
        })
      }else {
        animation.scale(0).step();
        this.setData({
          ani: animation.export()
        })
      }   
    }
  }
})

wxml文件

<view class="flyBox" hidden="{{flag}}" style="left: {{bus_x}}px; top: {{bus_y}}px;" animation="{{ani}}">
  <image src="{{imgUrl}}" class="img"></image>
</view>

2.调用组件home.js中的加入购物车事件

    //home.wxml
    <!--图片位置-->
    <view class="goods-image-tall" style="background-image:url('{{item.goodsListImg}}')" id="id{{item.goodsCode}}"></view>
    <!-- 加入购物的按钮-->
    <image catchtap="addGoods" data-goodsCode="{{item.goodsCode}}" data-type="RECOMEND_GOODS" data-goodsName="{{item.shopGoodsName}}" src='{{globalIcons.cart}}' 
            data-goods="{{item}}" data-from-type='{{0}}'  lazy-load data-id="id{{item.goodsCode}}" data-img="{{item.goodsListImg}}"></image>
    <!-- 飞入购物车动画 -->
    <fly-to-cart id="fly"></fly-to-cart>
    //home.js
      //添加商品到购物车
  addGoods(e) {
    app.debugInfo('---加入购物车---',e)
    let {
      goodscode,
      goodsname,
      type,
      optionList,
      goods,
      fromType,
      id,
      img
    } = e.currentTarget.dataset
    let that = this;
    //判断用户是否登录
    if (!app.globalData.token) {
      wx.navigateTo({
        url: '/pages/login/login',
      })
    } else {
      // 判断是否商品是否需要加工
      if (optionList && optionList.length > 0) {
        app.globalData.byValue = goods; //传递商品信息
        modalComponent = that.selectComponent('#cart-modal');
        modalComponent.showCartAction(fromType);
      } else {
        this.setData({
          dataType: type
        })
        let event = "ADD_TO_CART"
        let data = {
          "type": type,
          "goodsCode": goodscode,
          "goodsName": goodsname
        }
        dataPoint(event, data)
        // 获取图片位置
        fly = that.selectComponent('#fly'); // fly定义在home页面,页面卸载时记得清空
        let query = that.createSelectorQuery();
        query.select(`#${id}`).boundingClientRect();
        query.exec(function (imgInfo) {
          if (imgInfo[0]) {
            app.debugInfo('图片位置信息', imgInfo)
            fly.show({
              img,
              startPos:{
                x: Number.parseInt(imgInfo[0].width / 2 + imgInfo[0].left),
                y: Number.parseInt(imgInfo[0].height / 2 + imgInfo[0].top) 
              }
            });
          }
        })
        //不需要加工直接加入购物车
        addToCart({
          "goodsCode": goodscode, //商品编码
          "num": 1 //商品数量
        }).then(result => {
          wx.showToast({
            title: '加入购物车成功',
            icon: 'none',
            duration: 2000
          })
          countCartGoods()
        }).catch(err => {
          app.debugInfo("------ addCart failure -----", err)
        })
      }

    }
  },

最终效果:

参考:juejin.cn/post/684490…