JavaScript动画 —— 弹动动画

ScaNIV的头像 ScaNIV 0 2016-03-04 14:52 1

 基本信息

× 1    浏览数: 26342 分享时间: 2 年 前
1
缓动和弹动都是那对象从已有位置移动到目标位置的方法。但是缓动是指物体滑动到目标点就停下来;而弹动是指物体来回反弹一段时间后,最终停在目标点的运动。 弹动,大多数时候,物体的加速度与它到目标点的距离是成比例的。 来看一个在现实中弹动的例子:在橡皮筋的一头系上一个小球,另一头固定起来。小球的目标点就是它初始静止悬空的那个位置点。将小球拉开一小段距离然后松开,刚松手那一瞬间,它的速度为0,但是橡皮筋给它施加了外力,把它拉向目标点;如果小球尽可能地拉远,橡皮筋对它施加的外力就会变得越大。松手后,小球会急速飞过目标点。但是,当它飞过目标点以后,橡皮筋又把它往回拉,使其加速度减小,它飞得越远,橡皮筋施加的力就越大;最终,它的速度降为0,又掉头往回飞。由于受到摩擦力的影响,反复几次后,小球的运动逐渐慢下来,停在目标点上。

一. 一维坐标上的弹动

1 . 首先需要一个变量存储弹性比例系数,取值为0~1,较大的弹性比例常熟会表现出较硬的弹簧效果。 var spring = 0.1,     targetX = canvas.width / 2,     vx = 0; 2 . 接下来,计算小球到目标点的距离 var dx = targetX - ball.x; 3 . 计算加速度。在这个例子中,我们设置小球的加速度与距离成正比,即加速度 = 小球到目标点的距离 × 弹性比例系数。 var ax = dx * spring; 4 . 把加速度累加到速度上,然后把速度累加到小球的当前位置上: vx += ax; ball.x += vx; 在开始写代码前,先模拟一下整个过程,假设ball.x = 0,初速度vx = 0,目标点的位置targetX = 100,弹性比例系数spring = 0.1。下面是执行过程: (1) 第一轮,加速度ax = (100 - 0) * 0.1 = 10,把ax加载vx上得速度vx = 10,把vx加在小球的当前位置上得到ball.x = 10; (2) 第二轮,加速度ax = (100 - ball.x) * 0.1 = 9,由此得到vx = 10 + 9 = 19,ball.x = 10 + 19 = 29; (3) 第三轮,ax = 7.1, vx = 26.1,ball.x = 55.1; (4) 第四轮, ax = 4.49,vx = 30.59,ball.x = 85.69; (5) 第五轮, ax = 1.431,vx = 44.9,ball.x = 130.69: (6) 第六轮,ax = -3.069,vx = 41.831,ball.x = 88.859; ... ... 随着小球一帧一帧地靠近目标,加速度变得越来越小,但是速度一直在增加; 五轮过后,小球越过了目标点后,加速度变成反向加速度,并且逐渐增加,导致速度逐渐减小,最终速度为0后,反向加速度达到极大值。此时速度将变成反向速度。 HTML代码如下: <canvas id="canvas" width="600" height="100"></canvas> JavaScript代码如下: // requestAnimationFrame的兼容性写法 window.requestAnimFrame = (function(){     return  window.requestAnimationFrame       ||             window.webkitRequestAnimationFrame ||             window.mozRequestAnimationFrame    ||             window.oRequestAnimationFrame      ||             window.msRequestAnimationFrame     ||             function( callback ){                 window.setTimeout(callback, 1000 / 60);             }; })(); // cancelAnimationFrame的兼容性写法 window.cancelAnimationFrame = (function () {     return window.cancelAnimationFrame ||             window.webkitCancelAnimationFrame ||             window.mozCancelAnimationFrame ||             window.oCancelAnimationFrame ||             function (timer) {                 window.clearTimeout(timer);             }; })(); // 创建画球函数 function Ball() {     this.x = 0;     this.y = 0;     this.radius = 10;     this.fillStyle = "#f85455";     this.draw = function(cxt) {         cxt.fillStyle = this.fillStyle;         cxt.beginPath();         cxt.arc(this.x, this.y, this.radius,  0, 2 * Math.PI, true);         cxt.closePath();         cxt.fill();     } } var canvas = document.getElementById("canvas"),         context = canvas.getContext("2d"),         ball = new Ball(),         spring = 0.1,         targetX = canvas.width / 2,         vx = 0; ball.x = 20; ball.y = 20; // 缓动动画函数 var animRequest = null; (function drawFrame() {     animRequest = window.requestAnimationFrame(drawFrame, canvas);     context.clearRect(0, 0, canvas.width, canvas.height);     // 小球当前位置与目标点的距离     var dx = targetX - ball.x;     // 小球的加速度     var ax = dx * spring;     // 小球的速度     vx += ax;     // 计算出小球当前的位置     ball.x += vx;     ball.draw(context); })(); 实现的效果如下: See the Pen <a href="http://codepen.io/dengzhirong/pen/EVNWqL/" rel="nofollow" _href="http://codepen.io/dengzhirong/pen/EVNWqL/" rel="nofollow">EVNWqL</a> by dengzhirong (<a href="http://codepen.io/dengzhirong" rel="nofollow" _href="http://codepen.io/dengzhirong" rel="nofollow">@dengzhirong</a>) on <a href="http://codepen.io" rel="nofollow" _href="http://codepen.io" rel="nofollow">CodePen</a>. 但是!但是,问题是小球永远都不会停下来,因为小球的摆动幅度不变。而我们希望实现的例子中,小球的弹动会越来越慢,直到停止下来。在实际生活中,小球的弹动势能大多是由于摩擦力的存在而转化成内能,最后使小球停下。所以,在这里,我们也模拟摩擦力,创建摩擦力系数friction,取值范围为0~1。 var friction = 0.95; 然后把vx * friction,得到当前的速度vx。 vx * = friction; 最终效果如下: See the Pen <a href="http://codepen.io/dengzhirong/pen/GpNmRR/" rel="nofollow" _href="http://codepen.io/dengzhirong/pen/GpNmRR/" rel="nofollow">GpNmRR</a> by dengzhirong (<a href="http://codepen.io/dengzhirong" rel="nofollow" _href="http://codepen.io/dengzhirong" rel="nofollow">@dengzhirong</a>) on <a href="http://codepen.io" rel="nofollow" _href="http://codepen.io" rel="nofollow">CodePen</a>. 【备注:请按F5刷新,或者点击“Result”面板上悬浮的"Return"按钮后查看效果】

二. 二维坐标上的弹动

上面一个例子是让小球在x轴上运动。如果我们想让小球同时在x轴和y轴上运动,就需要引入二维坐标上的弹动。事实上很简单,只需要把目标点、速度和加速度扩展到二维坐标系上即可。 代码与上面例子雷同不再重复,直接上效果: See the Pen <a href="http://codepen.io/dengzhirong/pen/ojYWgm/" rel="nofollow" _href="http://codepen.io/dengzhirong/pen/ojYWgm/" rel="nofollow">ojYWgm</a> by dengzhirong (<a href="http://codepen.io/dengzhirong" rel="nofollow" _href="http://codepen.io/dengzhirong" rel="nofollow">@dengzhirong</a>) on <a href="http://codepen.io" rel="nofollow" _href="http://codepen.io" rel="nofollow">CodePen</a>. 【备注:请按F5刷新,或者点击“Result”面板上悬浮的"Return"按钮后查看效果】 与前一个例子唯一不同的是增加了一条y轴。但是现在小球看起来仍然像是一维运动,虽然小球同时在x轴和y轴上运动,但它仍然是一条直线。原因是它的初速度为0,也仅受一个把它拉向目标点的外力,所以它沿着直线运动。为了动画更丰富一点,可以尝试修改vx、vy或者不同x、y轴的friction值。自己尝试一下吧。

三. 目标点移动的弹动

目标点移动,我们很容易就想到把鼠标当成目标点。在上一篇介绍缓动动画时,有一个小球跟随鼠标的缓动动画。让小球跟随鼠标弹动同样很简单,只要把targetX和targetY替换为当前坐标即可。效果很炫酷,但是代码基本没变。只要在前面的例子中改动如下两行: var dx = targetX - ball.x; var dy = targetY - ball.y; 修改为: var dx = mouse.x - ball.x; var dy = mouse.y - ball.y; 当然,我们还需要写一个获得当前鼠标位置的函数,可以参考我写的博文《JavaScript动画详解(一) —— 循环与事件监听 完整代码如下: HTML代码: <canvas id="canvas" width="400" height="400"></canvas> JavaScript代码: // requestAnimationFrame的兼容性写法 window.requestAnimFrame = (function(){     return  window.requestAnimationFrame       ||             window.webkitRequestAnimationFrame ||             window.mozRequestAnimationFrame    ||             window.oRequestAnimationFrame      ||             window.msRequestAnimationFrame     ||             function( callback ){                 window.setTimeout(callback, 1000 / 60);             }; })(); // cancelAnimationFrame的兼容性写法 window.cancelAnimationFrame = (function () {     return window.cancelAnimationFrame ||             window.webkitCancelAnimationFrame ||             window.mozCancelAnimationFrame ||             window.oCancelAnimationFrame ||             function (timer) {                 window.clearTimeout(timer);             }; })(); // 获得当前鼠标位置 function getMouse(ev) {     var mouse = {         x: 0,         y: 0     };     var event = ev || window.event;     if(event.pageX || event.pageY) {         x = event.x;         y = event.y;     }else {         var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;         var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;         x = event.clientX + scrollLeft;         y = event.clientY + scrollTop;     }     mouse.x = x;     mouse.y = y;     return mouse; } // 创建画球函数 function Ball() {     this.x = 0;     this.y = 0;     this.radius = 10;     this.fillStyle = "#f85455";     this.draw = function(cxt) {         cxt.fillStyle = this.fillStyle;         cxt.beginPath();         cxt.arc(this.x, this.y, this.radius,  0, 2 * Math.PI, true);         cxt.closePath();         cxt.fill();     } } var canvas = document.getElementById("canvas"),         context = canvas.getContext("2d"),         ball = new Ball(),         spring = 0.05,         vx = 0,         vy = 0,         targetX = 0,         targetY = 0,         friction = 0.95; ball.x = 20; ball.y = 20; var mouse = {x: 0, y: 0}; canvas.addEventListener("mousemove", function(ev) {     mouse = getMouse(ev);     targetX = mouse.x;     targetY = mouse.y;     console.log(targetX + " , " + targetY); }, false); // 缓动动画函数 var animRequest = null; (function drawFrame() {     animRequest = window.requestAnimationFrame(drawFrame, canvas);     context.clearRect(0, 0, canvas.width, canvas.height);     // 小球当前位置与目标点的距离     var dx = targetX - ball.x;     var dy = targetY - ball.y;     // 小球的加速度     var ax = dx * spring;     var ay = dy * spring;     // 小球的速度     vx += ax;     vy += ay;     vx *= friction;     vy *= friction;     // 计算出小球当前的位置     ball.x += vx;     ball.y += vy;     ball.draw(context); })(); 效果如下: See the Pen <a href="http://codepen.io/dengzhirong/pen/LpbyGq/" rel="nofollow" _href="http://codepen.io/dengzhirong/pen/LpbyGq/" rel="nofollow">LpbyGq</a> by dengzhirong (<a href="http://codepen.io/dengzhirong" rel="nofollow" _href="http://codepen.io/dengzhirong" rel="nofollow">@dengzhirong</a>) on <a href="http://codepen.io" rel="nofollow" _href="http://codepen.io" rel="nofollow">CodePen</a>. 【备注:把鼠标移上去试试~】 好吧,上面这个例子不够带劲儿,希望使小球看起来像是栓在橡皮筋上,此时只要在上面的基础上再小球圆心与当前鼠标位置画线即可。 context.beginPath(); context.strokeStyle = "#71A4AD"; context.moveTo(ball.x, ball.y); context.lineTo(mouse.x, mouse.y); context.stroke(); 效果如下: See the Pen <a href="http://codepen.io/dengzhirong/pen/NGbjRd/" rel="nofollow" _href="http://codepen.io/dengzhirong/pen/NGbjRd/" rel="nofollow">NGbjRd</a> by dengzhirong (<a href="http://codepen.io/dengzhirong" rel="nofollow" _href="http://codepen.io/dengzhirong" rel="nofollow">@dengzhirong</a>) on <a href="http://codepen.io" rel="nofollow" _href="http://codepen.io" rel="nofollow">CodePen</a>.

四. 总结

弹动和缓动非常类似,都是使用循环函数逐帧绘制从当前位置到目标位置的运动效果。不同的是缓动是指速度与距离成比例,而弹动是加速度与距离成比例关系。但是要模拟出更加真实的弹动,可能需要加入类似摩擦力系数的因子,把速度逐渐降下,直到停止运动。 相关文章: 1 . 《JavaScript动画详解(二) —— 缓动动画 2 . 《JavaScript动画详解(一) —— 循环与事件监听

12 [下一页]
  • zft_2501的头像 zft_2501 2016-09-14 16:24 代码数:0
    get
  • cixexey的头像 cixexey 2018-05-21 17:42 代码数:0
    I really appreciate the kind of topics you post here. Thanks for sharing us a great information that is actually helpful. Good day! Colorado SEO services
  • cixexey的头像 cixexey 2018-05-22 18:20 代码数:0
    You know your projects stand out of the herd. There is something special about them. It seems to me all of them are really brilliant! Read more
  • banjue的头像 banjue 2017-04-28 16:49 代码数:0
    学到了
  • cixexey的头像 cixexey 2018-05-23 18:49 代码数:0
    Thanks for your insight for your fantastic posting. I’m glad I have taken the time to see this. moving companies
  • cixexey的头像 cixexey 2018-05-23 20:10 代码数:0
    I learn some new stuff from it too, thanks for sharing your information.Woodworking Sanders
  • lisa0221的头像 lisa0221 2017-05-17 15:10 代码数:0
    get!!
  • cixexey的头像 cixexey 2018-05-26 20:44 代码数:0
    The post is written in very a good manner and it contains many useful information for me. invisalign near me
  • cixexey的头像 cixexey 2018-05-26 22:54 代码数:0
    The post is written in very a good manner and it contains many useful information for me. invisalign near me
  • cixexey的头像 cixexey 2018-05-26 22:57 代码数:0
    The post is written in very a good manner and it contains many useful information for me. invisalign near me
您的评论: