JavaScript动画详解(二) —— 缓动动画
kart
8年前
<p>最普通的动画就是匀速的动画,每次增加固定的值。但是生活中很多运动并不是匀速运动的,而是有加速度改变的运动。在Web动画中,缓动动画有时候会让网站增色不少。</p> <p>在CSS3中可以使用ease, ease-in, ease-out, ease-in-out 或者 cubic-bezier(n,n,n,n)来实现缓动动画。而且目前也有一些jQuery封装了缓动动画的Move.js, Velocity.js和Tween.js等。在实际项目中使用这些库文件或者CSS3属性可以大大提高开发效率。但是在学习中,为了了解JS缓动动画的 真正原理,我觉得有必要尝试用原生的JS实现之。</p> <p>总的来说,缓动动画都是把对象从已有位置移动到目标位置的过程,在这个过程中,加速度或者速度会随与目标位置的远近而变化。</p> <p>缓动动画的一些具体动画曲线可以查看这里《<a href="/misc/goto?guid=4959660098781606599">缓动函数</a>》,感受一下~</p> <h2>一. 一般实现缓动的策略如下:</h2> <p>1 . 为运动确定一个比例系数,这是一个小于1且大于0的小数;</p> <p>2 . 确定目标点;</p> <p>3 . 计算出物体当前位置与目标点位置的距离;</p> <p>4 . 计算速度,例如缓入动画中,速度 = 距离 × 比例系数,这时比例系数为运动的加速度;</p> <p>5 . 用当前位置加上速度来计算新的位置;</p> <p>6 . 重复第3到第5步,知道物体到达目标;</p> <h3>1.1 例子:缓入动画</h3> <p>来个缓入动画例子我们分析一下,效果如下:</p> <p> </p> <p>先看看这些代码片段以及他们的含义:</p> <p>1 . 确定一个小数作为比例系数,这个比例系数为加速度(标量)。当系数越接近于1,物体移动得越快;当系数越接近于0,物体移动得越慢。</p> <pre> <code>var easing = 0.05; </code></pre> <p>2 . 确定目标点。这里用targetX和targetY来定义:</p> <pre> <code>var targetX = canvas.width / 2, targetY = canvas.height / 2; </code></pre> <p>3 . 计算物体到目标点的距离。创建小球名为ball,用ball的x、y减去目标点的x、y就能得到距离。</p> <pre> <code>var dx = targetX - ball.x, dy= targetY - ball.y; </code></pre> <p>4 . 速度 = 距离 × 比例系数。</p> <pre> <code>var vx = dx * easing, vy= dy * easing; </code></pre> <p>5 . 用当前位置加上速度来计算新的位置。</p> <pre> <code>ball.x += vx; ball.y += vy; </code></pre> <p>6 . 因为最后几步需要重复执行,所以会把这些代码放在drawFrame函数里面。</p> <p>完整代码如下:</p> <p>HTML代码:</p> <pre> <code><canvas id="canvas" width="400" height="100"></canvas> </code></pre> <p>JavaScript代码:</p> <pre> <code>// 创建画球函数 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(); } } // requestAnimationFrame的兼容性写法 window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function( callback ){ window.setTimeout(callback, 1000 / 60); }; })(); window.cancelAnimationFrame = (function () { return window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.oCancelAnimationFrame || function (timer) { window.clearTimeout(timer); }; })(); var canvas = document.getElementById("canvas"), context = canvas.getContext("2d"), ball = new Ball(), easing = 0.05, targetX = canvas.width - 10, targetY = canvas.height / 2; ball.x = 5; ball.y = 5; // 缓动动画函数 var animRequest = null; (function drawFrame() { animRequest = window.requestAnimationFrame(drawFrame, canvas); context.clearRect(0, 0, canvas.width, canvas.height); var vx = (targetX - ball.x) * easing; var vy = (targetY - ball.y) * easing; ball.x += vx; ball.y += vy; ball.draw(context); })(); </code></pre> <h3>1.2 改进版缓入动画:加入拖拽效果</h3> <p>加入了小球的鼠标移入判断和拖拽动画判断,然后就有了下面这个改进版的缓动动画了。效果如下:</p> <p> </p> <h2>二. 何时停止缓动动画</h2> <p>当计算一个单目标点的简单缓动时,物体最终会到达这个目标点,缓动也就完成了。但是,即使在前面的几个例子里,即使该物体看起来已经停止了,计算缓 动动画的代码还是一直在执行(不信的可以在缓动动画中加入打印代码如console.log("hello world!"),打开控制台就会看到健步如飞的"hello world!"会打印出来~)。这样比较浪费系统资源。一旦物体到达了目标点,代码就应该不再执行了。这个功能很简单,只需要在动画循环里面判断一下物体 是否到达目标点即可。如:</p> <pre> <code>if(ball.x === targetX && ball.y === targetY) { // 停止缓动动画代码 window.cancelAnimationFrame(animRequest); } </code></pre> <p>事实上,由于ball.x和ball.y可能是小数,随着vx和vy越来越小越趋近于0,事实上它离目标点越来越近,但是理论上永远不会到达目标 点,而是无穷趋于目标点的小数。一般分辨率的电脑的显示的最小精度是1px(除了一些高分屏精度为0.1px),不能精确显示无穷多位小数的距离。到底多 近才是足够近?这就需要判断物体到目标点的距离是否小于特定值了。我们可以根据实际情况使用Math.ceil()、Math.floor()或 Math.round()来对小数进行取整操作,以取接近目标点的值。</p> <p>所以上面的代码可改写为:</p> <pre> <code>if(Math.ceil(ball.x) === targetX && Math.ceil(ball.y) === targetY) { // 停止缓动动画代码 window.cancelAnimationFrame(animRequest); } </code></pre> <h2>三. 移动的目标点</h2> <p>在前面的例子中,目标点只有一个,并且是固定的。</p> <p>然而目标点可以是移动的。我们在每一帧都会重新计算距离,然后根据距离计算速度,代码并不关心物体是否到否目标点或者目标点是否在移动,它只需在播放的每一帧的时候知道目标点的位置,然后计算距离和速度。</p> <h3>3.1 例子:小球跟随鼠标运动</h3> <p>小球跟随鼠标运动的例子中,我们把鼠标位置作为目标点,只需要把前面例子中的targetX和targetY分别替换为鼠标的位置mouse.x和mouse.y即可。</p> <p> </p> <h2>四. 缓动的其他应用</h2> <p> </p> <p>缓动不仅仅适用于运动,还可以操作很多其他属性。只要这个属性是可以用数字表示的,就可以操作它。例如:</p> <h3>4.1 Demo1. 颜色缓动动画</h3> <p>尝试在24位颜色上使用缓动,要设置红、绿、蓝的初始值和目标值,用缓动改变每一种单独的颜色,然后再把他么合并为单个颜色。</p> <p>如下:</p> <pre> <code>// 初始化变量 var red = 255, green = 0, blue = 0, redTarget = 0, greenTarget = 0, blueTarget = 255; // 使用缓动动画 red += Math.ceil((redTarget - red) * easing); green += Math.ceil((greenTarget - green) * easing); blue += Math.ceil((blueTarget - blue) * easing); // 最后把这三个单色值合并成一个颜色 </code></pre> <h3>4.2 Demo2. 透明度缓动动画</h3> <p>将缓动应用在alpha上,设置alpha的初始值和目标值,然后使用缓动动画实现淡入淡出的效果,最后把它拼接成一个RGBA字符串:</p> <pre> <code>var alpha = 0, targetAlpha = 1; // 使用缓动动画 alpha += (targetAlpha - alpha) * easing; ball.fillStyle = "rgba(" + red +"," + green + "," + blue + "," + alpha + ")"; </code></pre> <p> </p> <h2>五. 高级缓动</h2> <p> </p> <p>我们上面用到的都是简单缓动,即物体只有一个加速度easing。而事实上我们可以完全可以通过使easing为非定值,来实现自定义物体的任意运动状态,譬如先加速且接近物体时减速等。</p> <p>一些高级缓动函数可以参考:</p> <p>1 . Tween.js的源码:<a href="/misc/goto?guid=4959677080353662793">https://github.com/tweenjs/tween.js/blob/master/src/Tween.js</a></p> <p>2 . jquery.easing.js的源码:<a href="/misc/goto?guid=4959677080439109543">https://github.com/gdsmith/jquery.easing/blob/master/jquery.easing.js</a></p> <h2>六. 总结</h2> <p>缓动动画是比例速度,通过修改每一帧的速度来计算出当前值,通过加速度easing可以控制独特的动画效果。简单缓动动画不难,关键是要动手练习。 高级缓动动画,可以自己实验出一种特效,或者多看看Tween.js和jquery.easing.js等一些类库的缓动动画实现以汲取经验。</p> <p> </p> <p>下一篇想写一下弹动动画。就酱纸。</p> <p>来自:http://www.dengzhr.com/frontend/html/494</p>