前言
之前写过一篇文章微信小程序Canvas绘图API,简单介绍了下微信小程序(下面统称小程序)Canvas绘图相关API的使用,然后写了一篇利用绘图知识绘制自定义参数二维码的文章微信小程序生成自定义参数二维码。今天,我们继续利用绘图相关API来实现标题所说的内容。先上效果图镇楼:
准备
首先要准备的主要是四张图片,相信大家一看就知道这四张图片的用途了
实现
这个效果看上去确实不错,那么具体是怎么实现的了?
原理
上面的三张心形图片分别绘制在三条不同的三阶贝塞尔曲线上,最终三条贝塞尔曲线汇集到一个相同的点。这三条贝塞尔曲线的起点和终点相同,然后分别通过两个不同的控制点来实现不同的路径效果。
布局
说到布局整个页面就两个元素,最下面的心形肯定是个image没跑,另外一个就是我们今天的主角:Cavans;接下来看布局文件:
<view>
<canvas canvas-id="mycanvas" class="canvas" />
<image src="../../images/heart_button.png" class="heart" bindtap="onClickImage" style="{{style_img}}"></image>
</view>
image中的style_img主要是为了实现点击image的时候的放大效果,暂时可以先不用关注,其他的写法没啥难度。
样式
.canvas {
background: transparent;
width: 90px;
height: 400px;
position: fixed;
right: 20px;
bottom: 60px;
}
/* transform下面的属性是为了让动画看上去更自然 */
.heart {
position: fixed;
right: 45px;
bottom: 30px;
width: 40px;
height: 40px;
transform: scale(1);
-webkit-transform: scale(1);
-webkit-transition: ease all;
-moz-transition: ease all;
transition: ease all;
-webkit-transition-duration: 700ms;
-moz-transition-duration: 700ms;
transition-duration: 700ms;
}
这里将canvas的高度设置成400px,宽度设置为90px,然后position设置为fixed(不知道fixed作用的请戳这里:微信小程序布局技巧(二)),最后设置底部心形的位置使之在canvas底部中间位置。如下图:
逻辑实现
要实现逻辑我们首先要找到的就是三条贝塞尔曲线,三阶贝塞尔曲线包括起点、终点和两个控制点(三阶贝塞尔曲线本文不做重点科普,不懂的童鞋去度娘)。从文章最开始效果图看,刚开始所有的心形都是从最底部心形的位置冒出来的,所以起点我们很好确定x:30,y:400,x坐标通过计算得出(90+25)-45-40,y坐标自然是canvas高度。而终点坐标则更简单了x:30,y:0,因为最终都汇集在canvas顶部,y坐标自然为0。但是中间控制点的坐标则没有这么有规律了,需要根据你想要的效果去慢慢尝试。本文中的三条贝塞尔曲线的参数如下:
[
[{
x: 30,
y: 400
}, {
x: 70,
y: 300
}, {
x: -50,
y: 150
}, {
x: 30,
y: 0
}],
[{
x: 30,
y: 400
}, {
x: 30,
y: 300
}, {
x: 80,
y: 150
}, {
x: 30,
y: 0
}],
[{
x: 30,
y: 400
}, {
x: 0,
y: 90
}, {
x: 80,
y: 100
}, {
x: 30,
y: 0
}]
]
这为了便于操作放入了一个二维数组,接下来就是js代码实现
var lastFrameTime = 0;
var count = 0;
var ctx = null;
var factor = {
speed: .008, // 运动速度,值越小越慢
t: 0 // 贝塞尔函数系数
};
var that;
var timer = null; // 循环定时器
Page({
data: {
style_img: '',
img_path: [
[{
x: 30,
y: 400
}, {
x: 70,
y: 300
}, {
x: -50,
y: 150
}, {
x: 30,
y: 0
}],
[{
x: 30,
y: 400
}, {
x: 30,
y: 300
}, {
x: 80,
y: 150
}, {
x: 30,
y: 0
}],
[{
x: 30,
y: 400
}, {
x: 0,
y: 90
}, {
x: 80,
y: 100
}, {
x: 30,
y: 0
}]
]
//这里是贝塞尔曲线参数
},
onLoad: function (options) {
that = this
//获取canvas实例
ctx = wx.createCanvasContext('mycanvas')
},
onUnload: function () {
if (timer != null) {
clearTimeout(timer)
}
},
//不断绘制图片到cavans
requestAnimationFrame(callback) {
var currTime = new Date().getTime();
//手机屏幕刷新率一般为60Hz,大概16ms刷新一次,这里为了使页面看上去更流畅自然,通过改变timedis的值可以控制动画的快慢
var timedis = 16 - (currTime - lastFrameTime)
var timeToCall = Math.max(0, timedis);
var id = setTimeout(callback, timeToCall);
lastFrameTime = currTime + timeToCall;
return id;
},
drawImage: function (data,repeatcount) {
if (repeatcount == 0){
return
}
var p10 = data[0][0]; // 三阶贝塞尔曲线起点坐标值
var p11 = data[0][1]; // 三阶贝塞尔曲线第一个控制点坐标值
var p12 = data[0][2]; // 三阶贝塞尔曲线第二个控制点坐标值
var p13 = data[0][3]; // 三阶贝塞尔曲线终点坐标值
var p20 = data[1][0];
var p21 = data[1][1];
var p22 = data[1][2];
var p23 = data[1][3];
var p30 = data[2][0];
var p31 = data[2][1];
var p32 = data[2][2];
var p33 = data[2][3];
var t = factor.t;
/*计算多项式系数*/
var cx1 = 3 * (p11.x - p10.x);
var bx1 = 3 * (p12.x - p11.x) - cx1;
var ax1 = p13.x - p10.x - cx1 - bx1;
var cy1 = 3 * (p11.y - p10.y);
var by1 = 3 * (p12.y - p11.y) - cy1;
var ay1 = p13.y - p10.y - cy1 - by1;
/*计算xt yt坐标值 */
var xt1 = ax1 * (t * t * t) + bx1 * (t * t) + cx1 * t + p10.x;
var yt1 = ay1 * (t * t * t) + by1 * (t * t) + cy1 * t + p10.y;
/** 计算多项式系数*/
var cx2 = 3 * (p21.x - p20.x);
var bx2 = 3 * (p22.x - p21.x) - cx2;
var ax2 = p23.x - p20.x - cx2 - bx2;
var cy2 = 3 * (p21.y - p20.y);
var by2 = 3 * (p22.y - p21.y) - cy2;
var ay2 = p23.y - p20.y - cy2 - by2;
/*计算xt yt坐标值*/
var xt2 = ax2 * (t * t * t) + bx2 * (t * t) + cx2 * t + p20.x;
var yt2 = ay2 * (t * t * t) + by2 * (t * t) + cy2 * t + p20.y;
/** 计算多项式系数*/
var cx3 = 3 * (p31.x - p30.x);
var bx3 = 3 * (p32.x - p31.x) - cx3;
var ax3 = p33.x - p30.x - cx3 - bx3;
var cy3 = 3 * (p31.y - p30.y);
var by3 = 3 * (p32.y - p31.y) - cy3;
var ay3 = p33.y - p30.y - cy3 - by3;
/*计算xt yt坐标值*/
var xt3 = ax3 * (t * t * t) + bx3 * (t * t) + cx3 * t + p30.x;
var yt3 = ay3 * (t * t * t) + by3 * (t * t) + cy3 * t + p30.y;
factor.t += factor.speed;
ctx.drawImage("../../images/heart1.png", xt1, yt1, 30, 30);
ctx.drawImage("../../images/heart2.png", xt2, yt2, 30, 30);
ctx.drawImage("../../images/heart3.png", xt3, yt3, 30, 30);
ctx.draw();
if (factor.t > 1) {
factor.t = 0;
clearTimeout(timer)
if(repeatcount <= -1){
that.startTimer(repeatcount)
}else{
if (count < repeatcount){
that.startTimer(repeatcount)
count++
}else {
that.draworiginal()
count = 0;
}
}
} else {
timer = that.requestAnimationFrame(function () {
that.drawImage(that.data.img_path, repeatcount)
})
}
},
onClickImage: function (e) {
//点击心形的时候动画效果
that.setData({
style_img: 'transform:scale(1.3);'
})
setTimeout(function () {
that.setData({
style_img: 'transform:scale(1);'
})
}, 500)
factor.t = 2
count = 0
that.startTimer(1)
},
//repeatcount -1就是循环,其他大于零的整数就是动画循环次数
startTimer: function (repeatcount) {
that.drawImage(that.data.img_path, repeatcount)
},
draworiginal(){
ctx.drawImage("../../images/heart1.png", 30, 400, 30, 30);
ctx.drawImage("../../images/heart2.png", 30, 400, 30, 30);
ctx.drawImage("../../images/heart3.png", 30, 400, 30, 30);
ctx.draw();
}
})
上面注释都写的很清楚了,我们再梳理下流程:
- 通过底部心形的点击事件,然后开启一个定时器。
- 然后在定时器中不断的改变贝塞尔函数系数来计算三条不同贝塞尔曲线不同时刻的点坐标,最后将图片绘制到canvas上面。
- 贝塞尔函数系数大于1的时候则停止绘制动画,否则递归调用drawImage函数来绘制图片到不同位置。
- requestAnimationFrame函数用来控制动画的时间长短
- draworiginal函数将图片都画到原始位置
最后我们看下repeatcount 为1的情况
点我用开发者工具打开
尾巴
马上快到伟大祖国70华诞了,借用此篇文章再次给我们伟大的祖国点赞,祝愿祖国的明天更加美好!
老规矩如果你喜欢我的文章,欢迎给我点赞,评论,谢谢!!