最普通的动画就是匀速的动画,每次增加固定的值。但是生活中很多运动并不是匀速运动的,而是有加速度改变的运动。在Web动画中,缓动动画有时候会让网站增色不少。
在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实现之。
总的来说,缓动动画都是把对象从已有位置移动到目标位置的过程,在这个过程中,加速度或者速度会随与目标位置的远近而变化。
缓动动画的一些具体动画曲线可以查看这里《缓动函数》,感受一下~
1 . 为运动确定一个比例系数,这是一个小于1且大于0的小数;
2 . 确定目标点;
3 . 计算出物体当前位置与目标点位置的距离;
4 . 计算速度,例如缓入动画中,速度 = 距离 × 比例系数,这时比例系数为运动的加速度;
5 . 用当前位置加上速度来计算新的位置;
6 . 重复第3到第5步,知道物体到达目标;
来个缓入动画例子我们分析一下,效果如下:
See the Pen <a href="http://codepen.io/dengzhirong/pen/bVBwXp/" _href="http://codepen.io/dengzhirong/pen/bVBwXp/">bVBwXp</a> by dengzhirong (<a href="http://codepen.io/dengzhirong" _href="http://codepen.io/dengzhirong">@dengzhirong</a>) on <a href="http://codepen.io" _href="http://codepen.io">CodePen</a>.【备注:请刷新后查看,或者点击Result中的“return”按钮】
先看看这些代码片段以及他们的含义:
1 . 确定一个小数作为比例系数,这个比例系数为加速度(标量)。当系数越接近于1,物体移动得越快;当系数越接近于0,物体移动得越慢。
var easing = 0.05;
2 . 确定目标点。这里用targetX和targetY来定义:
var targetX = canvas.width / 2, targetY = canvas.height / 2;
3 . 计算物体到目标点的距离。创建小球名为ball,用ball的x、y减去目标点的x、y就能得到距离。
var dx = targetX - ball.x, dy= targetY - ball.y;
4 . 速度 = 距离 × 比例系数。
var vx = dx * easing, vy= dy * easing;
5 . 用当前位置加上速度来计算新的位置。
ball.x += vx; ball.y += vy;
6 . 因为最后几步需要重复执行,所以会把这些代码放在drawFrame函数里面。
完整代码如下:
HTML代码:
<canvas id="canvas" width="400" height="100"></canvas>
JavaScript代码:
// 创建画球函数 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); })();
加入了小球的鼠标移入判断和拖拽动画判断,然后就有了下面这个改进版的缓动动画了。效果如下:
See the Pen <a href="http://codepen.io/dengzhirong/pen/GpNNrp/" _href="http://codepen.io/dengzhirong/pen/GpNNrp/">GpNNrp</a> by dengzhirong (<a href="http://codepen.io/dengzhirong" _href="http://codepen.io/dengzhirong">@dengzhirong</a>) on <a href="http://codepen.io" _href="http://codepen.io">CodePen</a>.【备注:请刷新后查看,或者点击Result中的“return”按钮】
当计算一个单目标点的简单缓动时,物体最终会到达这个目标点,缓动也就完成了。但是,即使在前面的几个例子里,即使该物体看起来已经停止了,计算缓动动画的代码还是一直在执行(不信的可以在缓动动画中加入打印代码如console.log("hello world!"),打开控制台就会看到健步如飞的"hello world!"会打印出来~)。这样比较浪费系统资源。一旦物体到达了目标点,代码就应该不再执行了。这个功能很简单,只需要在动画循环里面判断一下物体是否到达目标点即可。如:
if(ball.x === targetX && ball.y === targetY) { // 停止缓动动画代码 window.cancelAnimationFrame(animRequest); }
事实上,由于ball.x和ball.y可能是小数,随着vx和vy越来越小越趋近于0,事实上它离目标点越来越近,但是理论上永远不会到达目标点,而是无穷趋于目标点的小数。一般分辨率的电脑的显示的最小精度是1px(除了一些高分屏精度为0.1px),不能精确显示无穷多位小数的距离。到底多近才是足够近?这就需要判断物体到目标点的距离是否小于特定值了。我们可以根据实际情况使用Math.ceil()、Math.floor()或Math.round()来对小数进行取整操作,以取接近目标点的值。
所以上面的代码可改写为:
if(Math.ceil(ball.x) === targetX && Math.ceil(ball.y) === targetY) { // 停止缓动动画代码 window.cancelAnimationFrame(animRequest); }
在前面的例子中,目标点只有一个,并且是固定的。
然而目标点可以是移动的。我们在每一帧都会重新计算距离,然后根据距离计算速度,代码并不关心物体是否到否目标点或者目标点是否在移动,它只需在播放的每一帧的时候知道目标点的位置,然后计算距离和速度。
小球跟随鼠标运动的例子中,我们把鼠标位置作为目标点,只需要把前面例子中的targetX和targetY分别替换为鼠标的位置mouse.x和mouse.y即可。
效果如下:
See the Pen <a href="http://codepen.io/dengzhirong/pen/PPbbEG/" _href="http://codepen.io/dengzhirong/pen/PPbbEG/">PPbbEG</a> by dengzhirong (<a href="http://codepen.io/dengzhirong" _href="http://codepen.io/dengzhirong">@dengzhirong</a>) on <a href="http://codepen.io" _href="http://codepen.io">CodePen</a>.【备注:请刷新后查看,或者点击Result中的“return”按钮】
缓动不仅仅适用于运动,还可以操作很多其他属性。只要这个属性是可以用数字表示的,就可以操作它。例如:
尝试在24位颜色上使用缓动,要设置红、绿、蓝的初始值和目标值,用缓动改变每一种单独的颜色,然后再把他么合并为单个颜色。
如下:
// 初始化变量 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); // 最后把这三个单色值合并成一个颜色 ball.fillStyle = "rgb(" + red +"," + green + "," + blue + ")";
效果如下:
See the Pen <a href="http://codepen.io/dengzhirong/pen/MabbPq/" _href="http://codepen.io/dengzhirong/pen/MabbPq/">MabbPq</a> by dengzhirong (<a href="http://codepen.io/dengzhirong" _href="http://codepen.io/dengzhirong">@dengzhirong</a>) on <a href="http://codepen.io" _href="http://codepen.io">CodePen</a>.【备注:请刷新后查看,或者点击Result中的“return”按钮】
将缓动应用在alpha上,设置alpha的初始值和目标值,然后使用缓动动画实现淡入淡出的效果,最后把它拼接成一个RGBA字符串:
var alpha = 0, targetAlpha = 1; // 使用缓动动画 alpha += (targetAlpha - alpha) * easing; ball.fillStyle = "rgba(" + red +"," + green + "," + blue + "," + alpha + ")";
效果如下:
See the Pen <a href="http://codepen.io/dengzhirong/pen/WQooLd/" _href="http://codepen.io/dengzhirong/pen/WQooLd/">WQooLd</a> by dengzhirong (<a href="http://codepen.io/dengzhirong" _href="http://codepen.io/dengzhirong">@dengzhirong</a>) on <a href="http://codepen.io" _href="http://codepen.io">CodePen</a>.【备注:请刷新后查看,或者点击Result中的“return”按钮】
我们上面用到的都是简单缓动,即物体只有一个加速度easing。而事实上我们可以完全可以通过使easing为非定值,来实现自定义物体的任意运动状态,譬如先加速且接近物体时减速等。
一些高级缓动函数可以参考:
1 . Tween.js的源码:https://github.com/tweenjs/tween.js/blob/master/src/Tween.js
2 . jquery.easing.js的源码:https://github.com/gdsmith/jquery.easing/blob/master/jquery.easing.js
缓动动画是比例速度,通过修改每一帧的速度来计算出当前值,通过加速度easing可以控制独特的动画效果。简单缓动动画不难,关键是要动手练习。高级缓动动画,可以自己实验出一种特效,或者多看看Tween.js和jquery.easing.js等一些类库的缓动动画实现以汲取经验。