JavaScript动画详解(二) —— 缓动动画

kart 2年前
   <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>