前言
一提小程序与动画,首先想到的是什么?嗯,微信小程序独创了一套动画玩法,官方支持3种动画方案,分别是 createAnimation
、 this.animate
和 CSS3动画
。
1. createAnimation
与 Animation
创建一个动画实例animation。调用实例的方法来描述动画。最后通过动画实例的export方法导出动画数据传递给组件的animation属性。
var animation = wx.createAnimation({ transformOrigin: "50% 50%", duration: 1000, timingFunction: "ease", delay: 0 }) // step() 表示一组动画的完成,可以在一组动画中调用任意多个动画方法 // 一组动画中的所有动画会同时开始,一组动画完成后才会进行下一组动画 animation.translate(150, 0).rotate(180).step() animation.opacity(0).scale(0).step() this.setData({ animationData: animation.export() })
2. 关键帧动画 this.animate
接口
从小程序基础库 2.9.0 开始支持一种更友好的动画创建方式,用于代替旧的 wx.createAnimation 。它具有更好的性能和更可控的接口。在页面或自定义组件中,当需要进行关键帧动画时,可以使用 this.animate 接口。
this.animate(selector, keyframes, duration, callback)
官方给出的例子:
this.animate('#container', [ { opacity: 1.0, rotate: 0, backgroundColor: '#FF0000' }, { opacity: 0.5, rotate: 45, backgroundColor: '#00FF00'}, { opacity: 0.0, rotate: 90, backgroundColor: '#FF0000' }, ], 5000, function () { this.clearAnimation('#container', { opacity: true, rotate: true }, function () { console.log("清除了#container上的opacity和rotate属性") }) }.bind(this))
3. css3动画
这是界面动画的常见方式,CSS 动画运行效果良好,甚至在低性能的系统上。渲染引擎会使用跳帧或者其他技术以保证动画表现尽可能的流畅。
利用样式实现小程序动画,用法和css用法相似,定义好指定的动画类名后给元素加上即可。
这是一个模仿心跳的动画:
@keyframes heartBeat { 0% { transform: scale(1); } 14% { transform: scale(1.3); } 28% { transform: scale(1); } 42% { transform: scale(1.3); } 70% { transform: scale(1); } } .heartBeat { animation-name: heartBeat; animation-duration: 1.3s; animation-timing-function: ease-in-out; }
一、购物车商品曲线飞入动画
主要应用到的技术点:
1、小程序wxss布局,以及数据绑定
2、js二次bezier曲线算法
核心算法,写在app.js里
bezier: function (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 = [];
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 = {};
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
};
}
注释很详细,算法的原理其实也很简单。 源码也发出来吧,github地址:github.com/xiongchenf/…
调用方法和用法就不占篇幅了,都是基础的东西。
二、模块移动动画
动画效果:
下图有两组动画,分别为 api
方式(上)与 css3
方式(下)完成的效果,点击move按钮,动画启动。
代码实现
以下分别为 css3
与 api
的核心代码:
css3:
复制代码
// scss @mixin movePublic($oldLeft,$oldTop,$left,$top) { from { transform:translate($oldLeft,$oldTop); } to { transform:translate($left,$top); } } @mixin blockStyle($color,$name) { background: $color; animation:$name 2s linear infinite alternate; } .one { @include blockStyle(lightsalmon,onemove); } @keyframes onemove { @include movePublic(50rpx,-25rpx,-150rpx,0rpx); } .two { @include blockStyle(lightblue,twomove); } @keyframes twomove { @include movePublic(0rpx,25rpx,-50rpx,0rpx); } .three { @include blockStyle(lightgray,threemove); } @keyframes threemove { @include movePublic(0rpx,25rpx,50rpx,0rpx); } .four { @include blockStyle(grey,fourmove); } @keyframes fourmove { @include movePublic(-50rpx,-25rpx,150rpx,0rpx); } 复制代码
// js moveFunction(){ this.setData({ isMove: true }) } 复制代码
css3
中通过动态改变 class
类名来达到动画的效果,如上代码通过 one
、 two
、 three
、 four
来分别控制移动的距离,通过sass可以避免代码过于冗余的问题。 (纠结如何在小程序中使用 sass
的童鞋请看这里哦: wechat-mina-template )
api:
moveClick(){ this.move(-75,-12.5,25,'moveOne'); this.move(-25,12.5, 0,'moveTwo'); this.move(25, 12.5,0,'moveThree'); this.move(75, -12.5,-25,'moveFour'); this.moveFunction(); // 该事件触发css3模块进行移动 }, // 模块移动方法 move: function (w,h,m,ele) { let self = this; let moveFunc = function () { let animation = wx.createAnimation({ duration: 2000, delay: 0, timingFunction: "linear", }); animation.translate(w, 0).step() self.setData({ [ele]: animation.export() }) let timeout = setTimeout(function () { animation.translate(m, h).step(); self.setData({ // [ele] 代表需要绑定动画的数组对象 [ele]: animation.export() }) }.bind(this), 2000) } moveFunc(); let interval = setInterval(moveFunc,4000) } 复制代码
效果图可见,模块之间都是简单的移动,可以将他们的运动变化写成一个公共的事件,通过向事件传值,来移动到不同的位置。其中的参数 w,h,m,ele
分别表示发散水平方向移动的距离、聚拢时垂直方向、水平方向的距离以及需要修改 animationData
的对象。
通过这种方法产生的动画,无法按照原有轨迹收回,所以在事件之后设置了定时器,定义在执行动画2s之后,执行另一个动画。同时 动画只能执行一次 ,如果需要循环的动效,要在外层包裹一个重复执行的定时器到。
查看源码,发现 api
方式是通过 js
插入并改变内联样式来达到动画效果,下面这张动图可以清晰地看出样式变化。
打印出赋值的 animationData
, animates
中存放了动画事件的类型及参数; options
中存放的是此次动画的配置选项, transition
中存放的是 wx.createAnimation
调用时的配置, transformOrigin
是默认配置,意为以对象的中心为起点开始执行动画,也可在 wx.createAnimation
时进行配置。
三、音乐播放动画
上面的模块移动动画不涉及逻辑交互,因此新尝试了一个音乐播放动画,该动画需要实现暂停、继续的效果。
动画效果:
两组不同的动画效果对比,分别为 api
(上)实现与 css3
实现(下):
代码实现
以下分别是 css3
实现与 api
实现的核心代码:
css3:
复制代码
// scss .musicRotate{ animation: rotate 3s linear infinite; } @keyframes rotate{ from{ transform: rotate(0deg) } to{ transform: rotate(359deg) } } .musicPaused{ animation-play-state: paused; } 复制代码
// js playTwo(){ this.setData({ playTwo: !this.data.playTwo },()=>{ let back = this.data.backgroundAudioManager; if(this.data.playTwo){ back.play(); } else { back.pause(); } }) } 复制代码
通过 playTwo
这个属性来判断是否暂停,并控制 css
类的添加与删除。当为 false
时,添加 .musicPaused
类,动画暂停。
api:
复制代码
// js play(){ this.setData({ play: !this.data.play },()=>{ let back = this.data.backgroundAudioManager; if (!this.data.play) { back.pause(); // 跨事件清除定时器 clearInterval(this.data.rotateInterval); } else { back.play(); // 继续旋转,this.data.i记录了旋转的程度 this.musicRotate(this.data.i); } }) }, musicRotate(i){ let self = this; let rotateFuc = function(){ i++; self.setData({ i:i++ }); let animation = wx.createAnimation({ duration: 1000, delay: 0, timingFunction: "linear", }); animation.rotate(30*(i++)).step() self.setData({ musicRotate: animation.export() }); } rotateFuc(); let rotateInterval = setInterval( rotateFuc,1000 ); // 全局定时事件 this.setData({ rotateInterval: rotateInterval }) } 复制代码
通过 api
实现的方式是通过移除 animationData
来控制动画,同时暂停动画也需要清除定时器,由于清除定时器需要跨事件进行操作,所以定了一个全局方法 rotateInterval
。
api
方式定义了旋转的角度,但旋转到该角度之后便会停止,如果需要实现重复旋转效果,需要通过定时器来完成。因此定义了变量i,定时器每执行一次便加1,相当于每1s旋转30°,对 animation.rotate()
中的度数动态赋值。暂停之后继续动画,需要从原有角度继续旋转,因此变量i需要为全局变量。
代码变化
下图可以看出, api
方式旋转是通过不断累加角度来完成,而非 css3
中循环执行。
四、塔罗牌动画
目前还有个问题:android上面无卡顿,但是ios直接把微信卡掉! wxml
wxss
js
|