每周一点canvas动画代码文件
好像上次更新仍是十一前,这唰唰唰的就过去大半个月了,如今更新我也没什么很差意思的。此次咱们不涉及canvas 3D的内容,主要分享一个比较炫的动画效果,能够算是上一篇文章《每周一点canvas动画》——3D点线与水波动画的增强版。动画效果来自codePen。在这篇文章中咱们就分析这种效果是如何实现的,若是你对源码比较懵逼,相信看完解析就会恍然大悟。先上效果图:git
相比与上篇文章中简陋的水波动画的效果,本文的动画效果不只可以和鼠标进行交互,并且波浪的造成更加天然,更加符合物理规律。整个动画的造成过程就如动图中所展现的那样,在液面的位置点击鼠标,此处的液面就会有一个比较大的起伏,而后此处的震动会向两边传播,随着能量的衰减,后面的震动幅度会愈来愈下,最后能量衰减到零,页面趋于平静。听上去是否是很玄乎,感受很高深!毛主席告诉咱们千万不要被物体的表面现象所迷惑(谁知道是谁说的呢o(^▽^)o)。下面咱们就来一步一步的分析,这其中的原理。github
首先,在静止状态下咱们能够看到整个液面就至关因而个矩形。而当咱们点击液面的位置时,这个矩形就发生了相应的变化。但其实并非整个矩形都发生了变化,而只是矩形的上边发生了变化。那是如何作到仅仅让矩形的上边发生变化的呢?秘诀就在矩形的上边并非简单的从左边的点lineTo()
到右边的点。而是由不少的点lineTo()
组成。这样讲可能不太好理解,看图说话:canvas
在上部咱们设置了不少的点,这些点的纵坐标都是同样的,只是在水平方向相隔必定的间距。这样在静止的状态下,咱们就能够它看见与普通的矩形别无二致。而改变这些点的位置时咱们就能同时改变矩形的形状,从而造成不一样的效果。segmentfault
说到差分方程也许不少人会头疼,不过也没本法,疼就疼会吧!这个知识点在高数里讲微分方程那一节,若是不明白,就算了吧!记住下面的用法也不错,不过为了逼格咱们仍是简单的介绍下。数组
在数学上,递推关系(recurrence relation),也就是差分方程(difference equation),是一种递推地定义一个序列的方程式:序列的每一项目是定义为前一项的函数。某些简单定义的递推关系式可能会表现出很是复杂的(混沌的)性质,他们属于数学中的非线性分析领域。
记住一点,序列的每一项是定义为前一项的函数,咱们用的就是这个原理。他的图像若是用matalab来绘制就是下面这样:bash
只关注原函数,红色的那条曲线就能够了,是否是特别像水波。咱们要作的就是让那一堆点按照这样的波形去排列。函数
下面就到了你们最喜欢的代码时间。首先,咱们建立一个点类Vertexes, 它的做用就是定义并更新那一堆点,代码在vertex.js
中,以下:动画
function Vertex(x,y,baseY){ this.baseY = baseY; //基线 this.x = x; //点的坐标 this.y = y; this.vy = 0; //竖直方向的速度 this.targetY = 0; //目标位置 this.friction = 0.15; //摩擦力 this.deceleration = 0.95; //减速 } //y坐标更新 Vertex.prototype.updateY = function(diffVal){ this.targetY = diffVal + this.baseY; //改变目标位置 this.vy += (this.targetY - this.y); //速度 this.vy *= this.deceleration; this.y += this.vy * this.friction; //改变坐标竖直方向的位置 }
咱们要用这个函数去建立那一堆点。回到咱们的主文件index.js中。咱们先初始化一些要用的东西:this
var canvas = document.getElementById('canvas'), ctx = canvas.getContext('2d'), W = window.innerWidth; H = window.innerHeight; canvas.width = W; canvas.height = H; var color1 = "#6ca0f6", //矩形1的颜色 color2 = "#367aec"; //矩形2的颜色 var vertexes = [], //顶点坐标 verNum = 250, //顶点数 diffPt = [], //差分值
而后,建立点并把它push
进vertexes
中,同时也建立相应数量的差分值,一样把它放到diffPt
数组中,这样每一个点都有了对应的差分值。spa
for(var i=0; i<verNum; i++){ vertexes[i] = new Vertex(W/(verNum-1)*i, H/2, H/2); diffPt[i] = 0; //初始值都为0 }
结果是,每一个顶点的y
坐标都在(H/2
)的高度,水平坐标每隔必定的间隔取一个点。在这里是每隔4.5个像素取一个点,这与你canvas的宽度和点的数目有关。这样咱们就把点建立完成了,来绘制一下看看效果。
代码以下:
function draw(){ //矩形1 ctx.save() ctx.fillStyle = color1; ctx.beginPath(); ctx.moveTo(0, H); ctx.lineTo(vertexes[0].x, vertexes[0].y); for(var i=1; i<vertexes.length; i++){ ctx.lineTo(vertexes[i].x, vertexes[i].y); } ctx.lineTo(W,H); ctx.lineTo(0,H); ctx.fill(); ctx.restore(); //矩形2 ctx.save(); ctx.fillStyle = color2; ctx.beginPath(); ctx.moveTo(0, H); ctx.lineTo(vertexes[0].x, vertexes[0].y+5); for(var i=1; i<vertexes.length; i++){ ctx.lineTo(vertexes[i].x, vertexes[i].y+5); } ctx.lineTo(W, H); ctx.lineTo(0, H); ctx.fill(); ctx.restore(); }
就像你看到的那样此时咱们的液面彻底是静止的(由于没更新点嘛)。之因此要绘制两个矩形,你看看效果图就明白了,只是为了更好看,你彻底能够绘制第三层,第四层。下面咱们就来更新这些点的坐标。
点的更新咱们放在了update
函数中。首先,咱们设置一个初始的震荡点,缓冲变量和初始差分值。
var vPos = 125; //震荡点 var dd = 15; //缓冲 var autoDiff = 1000; //初始差分值
这里的震荡点就是咱们的起震位置,意思是vertexes
中的第125
号点开始起震,它对应的差分值就是autoDiff
。它的改变会引发其余点的变化,从而达到更新其余差分值的效果。
function update(){ autoDiff -= autoDiff*0.9; //1 diffPt[vPos] = autoDiff; //左侧 for(var i=vPos-1; i>0; i--){ //2 var d = vPos-i; if(d > dd){ d=dd; } diffPt[i]-=(diffPt[i] - diffPt[i+1])*(1-0.01*d); } //右侧 for(var i=vPos+1; i<verNum; i++){ //3 var d = i-vPos; if(d>dd){ d=dd; } diffPt[i] -= (diffPt[i] - diffPt[i-1])*(1-0.01*d); } //更新Y坐标 for(var i=0; i<vertexes.length; i++){ //4 vertexes[i].updateY(diffPt[i]); } }
如今咱们对上面的部分作详细解释:
代码1: 咱们设置了起震位置的差分偏移量为autoDiff=100
,注意autoDiff -= autoDiff*0.9;
, 也就是说它的值每一帧都会变化。
代码2:为起震位置的左边,主要关注diffPt[i]-=(diffPt[i] - diffPt[i+1])*(1-0.01*d);
这一行。i
的起始位置为124
,默认差分值为0
。稍做简单推算,你会发现,通过更新后第124
号点的差分值为99
,同理第123
号为97.02
。以此类推,咱们就能够获得第一帧的全部点的差分值。右边同理。
代码4:在获得第一帧的差分值后就该调用每一个点的更新函数了,而且传入计算好的差分值。造成的效果以下图所示
看一下updateY
函数,咱们把目标位置targetY
设置为差分值diffVal
和基线baseY
的和。而后,经过距离计算须要运动的速度vy
,最后将速度做用于点的纵坐标。这一段是否是与弹性动画缓动动画那一节很类似呢?
在缓冲系数dd的做用下,两侧的波会在扩散的过程当中愈来愈小,最后趋近于0.咱们也是经过这个变量去控制液体的粘度系数,达到粘稠度高的物体扩散的越缓慢而且起伏比较低,粘稠度低的物体扩散迅速但起伏大的效果。
随后,由于autoDiff
的不断衰减,不一样幅值波形的叠加造成波浪效果,最终衰减到0.液面也就趋于平静了。
如今,咱们把update()和draw()放入动画循环中你就会看到水波起伏而后趋于平静的效果。
(function drawframe(){ ctx.clearRect(0, 0, W, H); window.requestAnimationFrame(drawframe, canvas); update() draw(); })()
上面的代码已经实现了波浪动画的效果,可是震荡完成后就平静了,不会再发生震荡的效果。这一步咱们就来实现点哪,哪震的效果。实现的思路很简单:水波之因此区域平静是由于起震位置的差分值不断衰减的结果,咱们只须要在点击鼠标的位置重设autoDiff
就能够了。此外,起震点的位置也要变成鼠标点击的位置。代码以下:
canvas.addEventListener('mousedown', function(e){ var mouse = {x:null, y:null}; if(e.pageX||e.pageY){ mouse.x = e.pageX; mouse.y = e.pageY; }else{ mouse.x = e.clientX + document.body.scrollLeft +document.documentElement.scrollLeft; mouse.y = e.clientY + document.body.scrollTop +document.documentElement.scrollTop; } //重设差分值 if(mouse.y>(H/2-50) && mouse.y<(H/2 +50)){ autoDiff = 1000; vPos = 1 + Math.floor((verNum - 2) * mouse.x / W); diffPt[vPos] = autoDiff; } console.log(mouse.x, mouse.y) }, false)
在获取鼠标位置这里应该注意一点,咱们没有减去canvas的偏移量,这是由于在这里canvas作的是全屏设置。因此,若是你的画布并非全屏大小,建议你使用咱们的utils.js
文件中的方法captureMouse
来获取鼠标的坐标。
另外在判断鼠标是否点击在了液面上,咱们设定了一个比较宽的范围,上下共100px。这样作的目的是让用户很容易就能触发这个事件,而不是只在页面那惟一的一个值上才能触发。这种作法相信你之前作过,对于比较小的物体咱们会遮罩一个大一些的透明物体,而后在该物体上作事件的触发,便于用户操做。
其余的颜色改变等细小功能就不作过多的介绍了,see you!!!