基本实现思路
小程序要实现点击加购时绘制一个抛物线动画,这个抛物线动画是计算出来的贝塞尔曲线上每个点的坐标,然后通过`setData`动态给元素设置样式,从而实现动画的效果,但是频繁的调用`setData`会造成一定的性能问题,这里采用微信官方提供的`wxs`
wxs实现
<!-- 自定义动画 -->
<wxs module="customAnimate">
var curIndex = 0
var ballIns
var Ins
var curCoordIdx
var wxsFunction = function(event, ownerInstance) {
var ins = ownerInstance;
var topPoint = { x: 0, y: 0 }
// 起点
var finger = { x: event.detail.x, y: event.detail.y }
var cartIconInstance = ownerInstance.selectComponent('.cart-icon')
// 终点
var busPos = { x: cartIconInstance.getBoundingClientRect().x, y: cartIconInstance.getBoundingClientRect().y }
console.log('开始动画')
if (finger['y'] < busPos['y']) {
topPoint['y'] = finger['y'] - 80;
} else {
topPoint['y'] = busPos['y'] - 80;
}
topPoint['x'] = Math.abs(finger['x'] - busPos['x']) / 2;
if (finger['x'] > busPos['x']) {
topPoint['x'] = (finger['x'] - busPos['x']) / 2 + busPos['x'];
} else {
topPoint['x'] = (busPos['x'] - finger['x']) / 2 + finger['x'];
}
var linePos = bezier([busPos, topPoint, finger], 30)
coordArr = linePos.bezier_points
executeCartAnimation()
function executeCartAnimation () {
curCoordIdx = coordArr.length - 1
// 使用 requestAnimationFrame 代替定时器
ins.requestAnimationFrame(setStyleByFrame)
}
function setStyleByFrame() {
if (curCoordIdx >= 0) {
var scale = 0.6
if (curCoordIdx > 20) {
scale = curCoordIdx / coordArr.length
}
ins.selectComponent('.good_box').setStyle({
display: 'block',
left: coordArr[curCoordIdx].x + 'px',
top: coordArr[curCoordIdx].y + 'px',
transform: 'scale('+ scale +')'
})
console.log(curCoordIdx, coordArr.length)
curCoordIdx -= 1
ins.requestAnimationFrame(setStyleByFrame)
} else {
ins.selectComponent('.good_box').setStyle({
display: 'none'
})
}
}
}
function bezier(points, times) {
// 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 = [];
var DIST_AB = Math.sqrt(Math.pow(points[1]['x'] - points[0]['x'], 2) + Math.pow(points[1]['y'] - points[0]['y'], 2));
// 邻控制BC点间距
var DIST_BC = Math.sqrt(Math.pow(points[2]['x'] - points[1]['x'], 2) + Math.pow(points[2]['y'] - points[1]['y'], 2));
// D每次在AB方向上移动的距离
var EACH_MOVE_AD = DIST_AB / times;
// E每次在BC方向上移动的距离
var EACH_MOVE_BE = DIST_BC / times;
// 点AB的正切
var TAN_AB = (points[1]['y'] - points[0]['y']) / (points[1]['x'] - points[0]['x']);
// 点BC的正切
var TAN_BC = (points[2]['y'] - points[1]['y']) / (points[2]['x'] - points[1]['x']);
// 点AB的弧度值
var RADIUS_AB = Math.atan(TAN_AB);
// 点BC的弧度值
var 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 = {};
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 = {};
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': bezier_points
};
}
module.exports = {
wxsFunction: wxsFunction
}
</wxs>
<view catchtap="{{customAnimate.wxsFunction}}"></view>
与基本实现思路一致,不同的这里用`wxs`的高性能优势替代`setData`, 需要注意的是:wxs并不支持es6,不支持`setInterval`和`setTimeOut`,这里使用`requestAnimationFrame`这个api替代
最终测试测试在安卓端运行非常流畅,ios端表现不如安卓
最后附上微信官方文档 小程序框架 / 视图层 / 事件系统 / WXS 响应事件 (qq.com)