基于canvas实现物理运动效果与动画效果(一)
一、为什么要写这篇文章
某年某月某时某种原因,我在慕课网上看到了一个大神实现了关于小球的抛物线运动的代码,心中很是欣喜,故而写这篇文章来向这位大神致敬,同时也为了弥补自己在运动效果和动画效果制作方面的不足
二、几种简单的直线运动
这一部分主要讲解的是简单的运动效果的实现原理,其实所有的canvas动画效果的实现在核心思想是一致的:都是先定义个初始的状态,然后定义一个定时器,定时器内执行一个方法,记得在这个方法中要对当前的画面清除,然后在这个方法中重新绘制需要变化的效果,由于人眼存在残影,所以短时间内的中断的变化可以看成是连续的变化,这个就是canva动画运动原理
最简单的要从匀速直线运动说起,然后是匀加速直线运动。
匀速直线运动
HTML代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>canvas匀速直线运动</title> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <canvas id="canvas">你的浏览器不支持canvas,请跟换其他浏览器试一试</canvas> <script type="text/javascript" src="script.js"></script> </body> </html>
JS代码
window.onload=function(){ var canvas=document.getElementById('canvas'); canvas.height=728; canvas.width=1024; var context=canvas.getContext('2d'); context.fillStyle='red'; context.beginPath(); context.arc(800,300,30,0,2*Math.PI,true); context.closePath(); context.fill(); setInterval(function(){ run(context); }, 50); }; var speed=0; var startPoint=800; function run(cxt){ speed=-7; cxt.clearRect(0,0,1024,728); //cxt.top+=speed; startPoint+=speed; cxt.beginPath(); cxt.arc(startPoint,300,30,0,2*Math.PI,true); cxt.closePath(); cxt.fill(); }
运行效果如下:
PS:这里面画面有点卡顿,是录制的时候软件的因素造成的,直接运行上诉代码是可以看到正常运行的效果
重点代码分析:
var speed=0; var startPoint=800; function run(cxt){ speed=-7; cxt.clearRect(0,0,1024,728); //cxt.top+=speed; startPoint+=speed; cxt.beginPath(); cxt.arc(startPoint,300,30,0,2*Math.PI,true); cxt.closePath(); cxt.fill(); }
先把速度定义为0和获取开始点,然后将canvas画面的内容清除,接着是计算变化后的坐标,然后进行重绘(坐标重新计算是运动关键所在)
匀变速直线运动
匀变速直线运动的定义:在直线运动中,把加速度的大小和方向都不改变的运动(加速度为正时),称之为匀加速直线运动。
基本公式:
所以我们依次需要定义这样的几个变量加速度a,初始速度V0,位移量x,随时间变化的速度v,时间time,这几个变量的初始化值均为0,代码如下所示:
HTML代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>canvas匀加速直线运动</title> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <canvas id="canvas">你的浏览器不支持canvas,请跟换其他浏览器试一试</canvas> <script type="text/javascript" src="script.js"></script> </body> </html>
JavaScript代码:
window.onload=function(){ var canvas=document.getElementById('canvas'); canvas.height=728; canvas.width=1024; var context=canvas.getContext('2d'); context.fillStyle='red'; context.beginPath(); context.arc(800,300,30,0,2*Math.PI,true); context.closePath(); context.fill(); setInterval(function(){ run(context); }, 50); }; var v0=0;//初始速度 var a=0;//加速度 var v=0;//变化的速度 var time=0;//时间 var x=0;//位移量 var startPoint=800;//起始点 // V=V0+at // x=v0t+1/2at^2 // v^2-V^2=2ax function run(cxt){ time+=0.05; a=10; x=-(0.5*a*(time*time));//位移公式代入 startPoint+=x; cxt.clearRect(0,0,1024,728); cxt.beginPath(); cxt.arc(startPoint,300,30,0,2*Math.PI,true); cxt.closePath(); cxt.fill(); }
运行的效果如下:
基本上直线运动比较典型的也就是这两种,如有遗漏其他运动或者是需要博主讲解其他运动的制作的,请在留言板上留言
三、简单的曲线运动的实现
说到简单的曲线运动,我们就从最简单的也是我认为最基础的运动圆周运动说起
1、匀速圆周运动
圆周运动的定义:质点沿圆周运动,如果在任意相等的时间里通过的圆弧长度都相等,这种运动就叫做“匀速圆周运动”,亦称“匀速率圆周运动”。因为物体作圆周运动时速率不变,但速度方向随时发生变化。所以匀速圆周运动的线速度是每时每刻都在发生变化的。
匀速圆周运动的实现与分析
匀速圆周运动的实现第一反应我们会选择通过匀速圆周运动的物理公式进行计算得到,但是物理公式中没有哪条明确的公式是可以把单位时间的变化量和所在点的具体坐标相关联的,显然这样的一条思路是行不通的,从物理公式上面是来说是不具有可行性的,所以我们应该要换另外的一种方法来实现,这个时候我们应该要看透匀速圆周运动的本质,本质上来说,匀速圆周运动的实现其实就是通过一个原点,然后在这个原点的基础之上对一个物体进行360度的旋转。好的,相信对canvas api熟悉的小伙伴已经想到了,是的,我们可以通过旋转或者是矩阵来实现
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>canvas实现圆周运动</title> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <canvas id="canvas">你的浏览器不支持canvas,请跟换其他浏览器试一试</canvas> <script type="text/javascript" src="script.js"></script> </body> </html>
相关的JavaScript代码:
window.onload=function(){ var canvas=document.getElementById('canvas'); canvas.height=728; canvas.width=1024; var context=canvas.getContext('2d'); drawNotChange(context); context.fillStyle='blue'; context.beginPath(); context.arc(500,550,30,0,2*Math.PI,true); context.closePath(); context.fill(); setInterval(function(){ run(context); }, 50); }; var time=0;//定义运动的执行次数 function run(cxt){ cxt.clearRect(0,0,1024,728); drawNotChange(cxt); cxt.save();//将当前以左上角坐标为(0,0)的上下文环境进行保存,这样是为了在接下来中要进行画布偏移后,可以进行还原当前的环境 cxt.translate(500,400); cxt.rotate(time*8*Math.PI/180);//设定每次旋转的度数 cxt.fillStyle='blue'; cxt.beginPath(); cxt.arc(0,150,30,0,2*Math.PI,false); cxt.closePath(); cxt.fill(); cxt.restore();//将当前为(500,400)的点还原为(0,0),其实在save中就是将上下文环境保存到栈中,在restore下面对其进行还原 time+=1; } //绘制不变因素 function drawNotChange(context){ context.fillStyle='red'; context.beginPath(); context.arc(500,400,30,0,2*Math.PI,true); context.closePath(); context.fill(); context.beginPath(); context.arc(500,400,150,0,2*Math.PI,true); context.closePath(); context.stroke(); }
运行的结果如下:
为了让读者能够明白其中的原理,我会在注释中尽量将代码注释清楚
2、椭圆运动
可能有些小伙伴们对于高中的知识都已经遗忘了,但是这个不妨碍,因为在接下来我们会通过一步一步的复习相关数学知识最后才来实现效果,但是如果对高中知识比较熟悉的小伙伴,建议跳过这个阶段,直接看代码就行了,以免浪费时间
椭圆的定义:椭圆(Ellipse)是平面内到定点F1、F2的距离之和等于常数(大于|F1F2|)的动点P的轨迹,F1、F2称为椭圆的两个焦点。其数学表表达式为:|PF1|+|PF2|=2a(2a>|F1F2|)
椭圆图解:
长轴长我们用2a表示,短轴长我们用2b表示
假设椭圆的长轴与X轴平行,那么表达式如下所示:
根据三角函数之间的关系我们可以推导出:
x=a+cos(t)
y=b+sin(t)
这里面的t代表的是单位是单位时间内旋转的弧度
具体的推导会在以后有时间,为大家专门写一篇博文来讲解一些公式的推导过程
关于椭圆的数学知识已经讲完了,还是不太清楚的同学请执行去复习高中的知识,在这里就不再累赘了。
我们还开始代码实现之前还有先假设好一些参数,我们假设原点O(500,300),绕椭圆运动的物体为圆形半径为30,其中长半轴长a=200,短半轴长为b=100,每次重新获取物体的运动后的移动位置的时候,x都会变化一个单位,旋转为顺时针旋转,开始位置为原点的正左边的端点,最后原点我们以一个黑色且半径为10的小球表示。注意:上述的数据可以读者自行定义,但是要注意这个前提是必须保证a>b,如果a<b那么就不是这个公式了
我们先来实现椭圆的轨迹效果:
HTML代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>canvas实现椭圆运动</title> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <canvas id="canvas">你的浏览器不支持canvas,请跟换其他浏览器试一试</canvas> <script type="text/javascript" src="script.js"></script> </body> </html>
JavaScript代码:
var a=200, b=100, radius=30; window.onload=function(){ var canvas=document.getElementById('canvas'); canvas.height=668; canvas.width=1024; var cxt=canvas.getContext('2d'); cxt.beginPath(); cxt.arc(300,300,10,0,2*Math.PI,true) cxt.closePath(); cxt.fill(); route(cxt,300,300,200,100); }; //椭圆路线绘制 function route(context,x,y,a,b){ //max是等于1除以长轴值a和b中的较大者 //i每次循环增加1/max,表示度数的增加 //这样可以使得每次循环所绘制的路径(弧线)接近1像素 var step = (a > b) ? 1 / a : 1 / b; context.beginPath(); context.moveTo(x + a, y); //从椭圆的左端点开始绘制 for (var i = 0; i < 2 * Math.PI; i += step) { //参数方程为x = a * cos(i), y = b * sin(i), //参数为i,表示度数(弧度) context.lineTo(x + a * Math.cos(i), y + b * Math.sin(i)); } context.closePath(); context.stroke(); }
椭圆的轨迹的思路是:通过循环,将极小的线段首尾相连,绘制了一个类似于椭圆的一个图像,但是由于线段太过细小导致了我们肉眼看上去就成了一个椭圆
运行的效果是:
椭圆上小球的运动实现
这个的制作思路跟上面的思路是一样的,所以这里就不再分析
这次我们就先看一看效果如何:
HTML代码和上面的例子相同
JavaScript代码如下:
var a=200, b=100, radius=30; time=0;//循环的次数 window.onload=function(){ var canvas=document.getElementById('canvas'); canvas.height=768; canvas.width=1024; var cxt=canvas.getContext('2d'); centerPoint(cxt); arcRoute(cxt,300,300,a,b,radius); setInterval(function(){ arcRoute(cxt,300,300,a,b,radius); }, 70); }; //绘制原点 function centerPoint(cxt){ cxt.fillStyle="black"; cxt.beginPath(); cxt.arc(300,300,10,0,2*Math.PI,true) cxt.closePath(); cxt.fill(); } //椭圆路线绘制 function route(context,x,y,a,b){ var step = (a > b) ? 1 / a : 1 / b; context.beginPath(); context.moveTo(x + a, y); //从椭圆的左端点开始绘制 for (var i = 0; i < 2 * Math.PI; i += step) { context.lineTo(x + a * Math.cos(i), y + b * Math.sin(i)); } context.closePath(); context.stroke(); } //椭圆上小球运动的实现 function arcRoute(context,x,y,a,b,r){ context.clearRect(0,0,1024,768); route(context,x,x,a,b); centerPoint(context); var step = (a > b) ? 1 / a : 1 / b; context.fillStyle="red"; if(time==0){ context.beginPath(); context.arc(x,y,r,0,2*Math.PI,true); context.closePath(); context.fill(); }else{ context.beginPath(); context.arc(x+a*Math.cos(time),y+b*Math.sin(time),r,0,2*Math.PI,true); context.closePath(); context.fill(); } time+=1; }
四、相关的参考资料
好了,这一节的内容就先结束了,下一节预告,下一节:会谈谈一些其他的运动效果的实现和这些运动效果的另一种实现方式,同时还会有一些关于碰撞的检测等等的知识,敬请期待。如果有什么不懂或者是错误的地方,欢迎给位朋友在留言板留下你的想法,你的支持是我前进的动力