小程序商品加入购物车抛物线动画
<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%; }
那时候我只有一台录音机也没有电脑 也不敢奢求说唱会让自己的生活变好