小程序商品加入购物车抛物线动画

 

 

<view class="container">
  <!-- 商品list -->
  <view class="goods-list" wx:for="{{goodsList}}"  wx:key="keys" data-url="{{item}}"  bindtap="handleClick" >
    <image src="{{item}}" mode="widthFix" />
  </view>
  <!-- 动画图标 -->
  <view class="ani-icon" hidden="{{hideIcon}}" style="left: {{bezierX}}px; top: {{bezierY}}px;">
    <image src="{{currentImg}}" mode="widthFix" />
  </view>
  <!-- 购物车 -->
  <view class="bessel-curve">
    <image src="https://qiniu-image.qtshe.com/gwc.webp" mode="widthFix" />
    <view class="count" wx:if="{{showCount}}">{{count}}</view>
  </view>
</view>

 

Page({
  data: {
    showCount: false, //是否隐藏购物车上的数字
    count: 0, //购物车上的计数
    hideIcon: true, //是否隐藏物品小图标
    goodsList: ['https://qiniu-image.qtshe.com/1644982838931_893.png',
      'https://qiniu-image.qtshe.com/1618195443775_420.jpg',
      'https://qiniu-image.qtshe.com/1608622111246_855.jpg',
      'https://qiniu-image.qtshe.com/1600931215470_125.jpg',
      'https://qiniu-image.qtshe.com/1600930225808_878.jpg',
      'https://qiniu-image.qtshe.com/1645774771559_885.jpg',
      'https://qiniu-image.qtshe.com/1618196790225_336.jpg',
      'https://qiniu-image.qtshe.com/1606200021509_447.jpg',
      'https://qiniu-image.qtshe.com/1615971983771_806.jpg',
      'https://qiniu-image.qtshe.com/1578465001976_604.png',
      'https://qiniu-image.qtshe.com/1608622597692_304.jpg',
      'https://qiniu-image.qtshe.com/1602300969696_870.jpg',
      'https://qiniu-image.qtshe.com/1599794764011_974.jpg',
      'https://qiniu-image.qtshe.com/1609382179770_905.jpg',
      'https://qiniu-image.qtshe.com/1600935022384_636.jpg',
      'https://qiniu-image.qtshe.com/1605683029629_433.jpg'
    ]
  },

  onLoad() {
    /** 设置购物车的坐标 */
    wx.getSystemInfo({
      success: (res) => {
        let busPos = {};
        // x y 坐标分别取屏幕百分之八十的位置
        busPos['x'] = res.windowWidth * 0.8;
        busPos['y'] = res.windowHeight * 0.8;
        this.setData({
          busPos
        })
      }
    })
  },

  // 物品的点击事件
  handleClick(e) {
      // 如果ani-icon正在运动,则不处理
    if (!this.data.hideIcon) return;
    // 将当前点击的图片作为运动的icon图标
    const { dataset: { url = '' } = {} } = e.currentTarget
    this.setData({
      currentImg: url
    })
    
    //定义手指点击位置
    let finger = {};
    //定义动画顶点位置
    let topPoint = {};
    finger['x'] = e.touches[0].clientX;
    finger['y'] = e.touches[0].clientY;
    // 当手指点击位置y轴小于购物车位置时,顶点以手指点击位置向上移150
    if (finger['y'] < this.data.busPos['y']) {
      topPoint['y'] = finger['y'] - 150;
    } else {
      // 当手指点击位置y轴大于购物车位置时,顶点以购物车位置向上移150
      topPoint['y'] = this.data.busPos['y'] - 150;
    }
    //点击位置是否在购物车右面
    let isRight = false
    if (finger['x'] > this.data.busPos['x']) {
      //点击位置在购物车右面时,顶点y轴应该在购物车下方
      isRight = true
      topPoint['y'] = finger['y'] - Math.abs(this.data.busPos['y'] - finger['y'])
      //点击位置在购物车右面时,顶点x轴应该点击位置左侧
      topPoint['x'] = Math.abs(finger['x'] - this.data.busPos['x']) / 2 - finger['x'];
    } else {
      //点击位置在购物车左侧时,顶点x轴应该点击位置右侧
      topPoint['x'] = Math.abs(finger['x'] - this.data.busPos['x']) / 2 + finger['x'];
    }

    //计算出贝塞尔曲线运动位置数组
    let linePos = this.bezier([finger, topPoint, this.data.busPos], 30, isRight);
    this.setData({
      linePos,
      finger
    })
    //开始动画
    this.startAnimation(linePos);
  },

  /**
   * 开始动画
   * @method startAnimation
   * @param {object} e 事件
   */
  startAnimation() {
    var index = 0 //定义定时器执行次数
    let bezier_points = this.data.linePos['bezier_points'];

    this.setData({
      hideIcon: false,
      bezierX: this.data.finger['x'],
      bezierY: this.data.finger['y']
    })
    //开启定时器,依次按照贝塞尔曲线位置做动画位移
    this.timer = setInterval(() => {
      index++; //记录定时器执行次数
      this.setData({ //改变运动中图标位置
        bezierX: bezier_points[index]['x'],
        bezierY: bezier_points[index]['y']
      })

      if (index >= 28) {
        /** 定时器执行达到28次则关闭定时器 */
        clearInterval(this.timer)
        this.setData({
          hideIcon: true,
          showCount: true,
          count: this.data.count += 1
        })
      }
    }, 33);
  },
  bezier(points, times, isRight) {
    // 0、以3个控制点为例,点A,B,C,AB上设置点D,BC上设置点E,DE连线上设置点F,则最终的贝塞尔曲线是点F的坐标轨迹。
    // 1、计算相邻控制点间距。
    // 2、根据完成时间,计算每次执行时D在AB方向上移动的距离,E在BC方向上移动的距离。
    // 3、时间每递增100ms,则D,E在指定方向上发生位移, F在DE上的位移则可通过AD/AB = DF/DE得出。
    // 4、根据DE的正余弦值和DE的值计算出F的坐标。
    // 邻控制AB点间距
    var bezier_points = [];
    var points_D = [];
    var points_E = [];
    const DIST_AB = Math.sqrt(Math.pow(points[1]['x'] - points[0]['x'], 2) + Math.pow(points[1]['y'] - points[0]['y'], 2));
    // 邻控制BC点间距
    const DIST_BC = Math.sqrt(Math.pow(points[2]['x'] - points[1]['x'], 2) + Math.pow(points[2]['y'] - points[1]['y'], 2));
    // D每次在AB方向上移动的距离
    const EACH_MOVE_AD = DIST_AB / times;
    // E每次在BC方向上移动的距离 
    const EACH_MOVE_BE = DIST_BC / times;
    // 点AB的正切
    const TAN_AB = (points[1]['y'] - points[0]['y']) / (points[1]['x'] - points[0]['x']);
    // 点BC的正切
    const TAN_BC = (points[2]['y'] - points[1]['y']) / (points[2]['x'] - points[1]['x']);
    // 点AB的弧度值
    const RADIUS_AB = Math.atan(TAN_AB);
    // 点BC的弧度值
    const RADIUS_BC = Math.atan(TAN_BC);
    // 每次执行
    for (var i = 1; i <= times; i++) {
      // AD的距离
      var dist_AD = EACH_MOVE_AD * i;
      // BE的距离
      var dist_BE = EACH_MOVE_BE * i;
      // D点的坐标
      var point_D = {};
      if (isRight) {
        point_D['x'] = -dist_AD * Math.cos(RADIUS_AB) + points[0]['x'];
      } else {
        point_D['x'] = dist_AD * Math.cos(RADIUS_AB) + points[0]['x'];
      }

      point_D['y'] = dist_AD * Math.sin(RADIUS_AB) + points[0]['y'];
      points_D.push(point_D);
      // E点的坐标
      var point_E = {};
      if (isRight) {
        point_E['x'] = -dist_BE * Math.cos(RADIUS_BC) + points[1]['x'];
      } else {
        point_E['x'] = dist_BE * Math.cos(RADIUS_BC) + points[1]['x'];
      }

      point_E['y'] = dist_BE * Math.sin(RADIUS_BC) + points[1]['y'];
      points_E.push(point_E);
      // 此时线段DE的正切值
      var tan_DE = (point_E['y'] - point_D['y']) / (point_E['x'] - point_D['x']);
      // tan_DE的弧度值
      var radius_DE = Math.atan(tan_DE);
      // 此时DE的间距
      var dist_DE = Math.sqrt(Math.pow((point_E['x'] - point_D['x']), 2) + Math.pow((point_E['y'] - point_D['y']), 2));
      // 此时DF的距离
      var dist_DF = (dist_AD / DIST_AB) * dist_DE;
      // 此时DF点的坐标
      var point_F = {};
      point_F['x'] = dist_DF * Math.cos(radius_DE) + point_D['x'];
      point_F['y'] = dist_DF * Math.sin(radius_DE) + point_D['y'];
      bezier_points.push(point_F);
    }
    return {
      bezier_points
    }
  }
})
.container {
  width: 100%;
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  padding: 0 24rpx;
  box-sizing: border-box;
}

.goods-list {
  width: 340rpx;
  background: #fff;
}

.goods-list image {
  width: 100%;
}

.bessel-curve {
  width: 48px;
  height: 48px;
  position: fixed;
  left: 80%;
  top: 80%;
  background: #FFF;
  border-radius: 50%;
}

.bessel-curve  image {
  width: 100%;
  height: 100%;
  position: absolute;
  border-radius: 50%;
}

.count {
  display: block;
  height: 20px;
  line-height: 20px;
  font-size: 12px;
  background: #ff4611;
  padding: 0 6px;
  border-radius: 10px;
  color: #fff;
  position: absolute;
  left: 34px;
  top: 0;
}

.ani-icon {
  width: 40px;
  height: 40px;
  position: fixed;
  border-radius: 50%;
  overflow: hidden;
  left: 50%;
  top: 50%;
  z-index: 99;
}

.ani-icon image {
  display: block;
  width: 100%;
  height: 100%;
}

 

posted @ 2023-03-16 16:43  大熊丨rapper  阅读(153)  评论(0编辑  收藏  举报