最普通的动画就是匀速的动画,每次增加固定的值。但是生活中很多运动并不是匀速运动的,而是有加速度改变的运动。在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步,知道物体到达目标;

1.1 例子:缓入动画

来个缓入动画例子我们分析一下,效果如下:


【备注:请刷新后查看,或者点击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);
})();

1.2 改进版缓入动画:加入拖拽效果

加入了小球的鼠标移入判断和拖拽动画判断,然后就有了下面这个改进版的缓动动画了。效果如下:


【备注:请刷新后查看,或者点击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);
}

三. 移动的目标点

在前面的例子中,目标点只有一个,并且是固定的。

然而目标点可以是移动的。我们在每一帧都会重新计算距离,然后根据距离计算速度,代码并不关心物体是否到否目标点或者目标点是否在移动,它只需在播放的每一帧的时候知道目标点的位置,然后计算距离和速度。

3.1 例子:小球跟随鼠标运动

小球跟随鼠标运动的例子中,我们把鼠标位置作为目标点,只需要把前面例子中的targetX和targetY分别替换为鼠标的位置mouse.x和mouse.y即可。

效果如下:


【备注:请刷新后查看,或者点击Result中的“return”按钮】

四. 缓动的其他应用

缓动不仅仅适用于运动,还可以操作很多其他属性。只要这个属性是可以用数字表示的,就可以操作它。例如:

4.1 Demo1. 颜色缓动动画

尝试在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 + ")";

效果如下:


【备注:请刷新后查看,或者点击Result中的“return”按钮】

4.2 Demo2. 透明度缓动动画

将缓动应用在alpha上,设置alpha的初始值和目标值,然后使用缓动动画实现淡入淡出的效果,最后把它拼接成一个RGBA字符串:

var alpha = 0,
    targetAlpha = 1;

// 使用缓动动画
alpha += (targetAlpha - alpha) * easing;
ball.fillStyle = "rgba(" + red +"," + green + "," + blue + "," + alpha + ")";

效果如下:


【备注:请刷新后查看,或者点击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等一些类库的缓动动画实现以汲取经验。

下一篇想写一下弹动动画。就酱纸。

本文作者:子匠_Zijor,转载请注明出处:http://www.dengzhr.com/frontend/html/494