缓动动画和弹性动画都是 “把一个物体从已有位置移动到目标位置” 的方法。缓动动画指的是物体滑动到目标点就停下来了,而弹性动画指的是物体来回地反弹一会儿,最终停在目标点的运动。
## 缓动动画
缓动动画指的是带有一定缓冲效果的动画。在动画过程中,物体在某一段时间会 “渐进加速” 或 “渐进减速”,从而让物体运动看起来更为自然和逼真。
在 Canvas 中,想要实现缓动动画,一般需要以下五个步骤
(1) 定义一个 0 ~ 1 之间的缓动系数 easing
(2) 计算出物体与终点之间的距离
(3) 计算出当前速度,其中当前速度 = 距离 X 缓动系数
(4) 计算新的位置,其中新的位置 = 当前位置 + 当前速度
(5) 重复执行第 2 ~ 4 步,直到物体达到目标
伪代码:
```js
let targetX = 任意位置
let targetY = 任意位置
// 动画循环
let vx = (targetX - object.x) * easing
let vy = (targetY - object.y) * easing
```
### 例:x 轴或 y 轴方向的缓动动画
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>正圆运动</title>
<script src="../ball.js"></script>
<script src="../tool.js"></script>
</head>
<body>
<canvas id="canvas" width="480" height="300" style="border: 1px solid gray; display: block; margin: 0 auto;"></canvas>
<script>
window.onload = function () {
let cnv = document.getElementById('canvas')
let cxt = cnv.getContext('2d')
let ball = new Ball(0, cnv.height / 2)
// 定义终点的 x 轴坐标
let targetX = cnv.width * (3 / 4)
// 定义缓动系数
const easing = 0.05;
(function frame () {
window.requestAnimationFrame(frame)
cxt.clearRect(0, 0, cnv.width, cnv.height)
let vx = (targetX - ball.x) * easing
ball.x += vx
ball.fill(cxt)
})()
}
</script>
</body>
</html>
```
这个缓动动画的效果就是小球运动到终点的速度有一个由快到慢的过程,即随着距离的不断减小小球的运动速度也不断减小。缓动动画看上去和摩擦力的效果很像,但是两者也有本质上的区别:在摩擦力动画中,每一帧的当前速度等于上一帧速度乘以摩擦系数,其速度是按照固定比例改变的;而缓动动画中,每一帧的当前速度等于距离乘以缓动系数,其速度并不是按照固定比例改变的。
![](https://img.kancloud.cn/cc/f5/ccf58c72d91d01482d4a70bf5e36fb6e_641x405.gif =300x)
### 例:任意方向的缓动动画
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>缓动动画2</title>
<script src="../ball.js"></script>
<script src="../tool.js"></script>
</head>
<body>
<canvas id="canvas" width="480" height="300" style="border: 1px solid gray; display: block; margin: 0 auto;"></canvas>
<script>
window.onload = function () {
let cnv = document.getElementById('canvas')
let cxt = cnv.getContext('2d')
let ball = new Ball(0, 0)
// 定义终点的 x 轴坐标和 y 轴坐标
let targetX = cnv.width * (3 / 4)
let targetY = cnv.height * (1 / 2)
// 定义缓动系数
const easing = 0.05;
(function frame () {
window.requestAnimationFrame(frame)
cxt.clearRect(0, 0, cnv.width, cnv.height)
let vx = (targetX - ball.x) * easing
let vy = (targetY - ball.y) * easing
ball.x += vx
ball.y += vy
ball.fill(cxt)
})()
}
</script>
</body>
</html>
```
### 例:多个小球追随鼠标
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>多个小球追随鼠标</title>
<script src="../ball.js"></script>
<script src="../tool.js"></script>
</head>
<body>
<canvas id="canvas" width="480" height="300" style="border: 1px solid gray; display: block; margin: 0 auto;"></canvas>
<script>
window.onload = function () {
let cnv = document.getElementById('canvas')
let cxt = cnv.getContext('2d')
// 初始化数据
let bigBall = new Ball(cnv.width / 2, cnv.height / 2, 15, '#FF6699')
let smallBall = new Ball(cnv.width / 2, cnv.height / 2, 12, '#66CCFF')
let mouse = tools.getMouse(cnv)
const easing = 0.05;
(function frame () {
window.requestAnimationFrame(frame)
cxt.clearRect(0, 0, cnv.width, cnv.height)
// 第 1 个小球跟随鼠标移动
let vx1 = (mouse.x - bigBall.x) * easing
let vy1 = (mouse.y - bigBall.y) * easing
bigBall.x += vx1
bigBall.y += vy1
bigBall.fill(cxt)
// 第 2 个小球跟随第 1 个小球移动
let vx2 = (bigBall.x - smallBall.x) * easing
let vy2 = (bigBall.y - smallBall.y) * easing
smallBall.x += vx2
smallBall.y += vy2
smallBall.fill(cxt)
})()
}
</script>
</body>
</html>
```
![](https://img.kancloud.cn/1d/7d/1d7d0f723a11dd5f1ed56580f945fb62_641x405.gif =300x)
### 例:缓动动画应用
缓动动画不仅能用于物体的运动,其也可以用于物体的各种属性,包括大小、颜色、透明度及旋转等。
不管缓动动画应用于什么方面,其实现思路都是一样的,即以下两个步骤:
(1) 当前速度 = (最终值 - 当前值)X 缓动系数
(2) 新的值 = 当前值 + 当前速度
下面举一个缓动动画作用于颜色的例子:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>缓动动画2</title>
<script src="../ball.js"></script>
<script src="../tool.js"></script>
</head>
<body>
<canvas id="canvas" width="480" height="300" style="border: 1px solid gray; display: block; margin: 0 auto;"></canvas>
<script>
window.onload = function () {
let cnv = document.getElementById('canvas')
let cxt = cnv.getContext('2d')
// 初始化数据
let ball = new Ball(cnv.width / 2, cnv.height / 2, 30)
ball.fill(cxt)
const easing = 0.01
let red = 255
let green = 0
let blue = 0
let targetRed = 10
let targetGreen = 255
let targetBlue = 55;
(function frame () {
window.requestAnimationFrame(frame)
cxt.clearRect(0, 0, cnv.width, cnv.height)
let vRed = (targetRed - red) * easing
let vGreen = (targetGreen - green) * easing
let vBlue = (targetBlue - blue) * easing
red += vRed
green += vGreen
blue += vBlue
let color = `rgba(${red}, ${green}, ${blue}, 1.0)`
ball.color = color
ball.fill(cxt)
})()
}
</script>
</body>
</html>
```
![](https://img.kancloud.cn/18/ee/18eef6fdf298eed3123651a6d064cca2_641x405.gif =300x)
## 弹性动画
弹性动画和缓动动画是非常相似的,它们的实现都是 “把一个物体从一个位置移动到另一个位置” 的动画效果。不过这两者也有明显的区别:在缓动动画中,物体滑动到终点就停下来了,而在弹性动画动画中,物体滑动到终点后还会来回反弹一会儿,直至停止。
从技术上说,缓动动画和弹性动画有以下几个共同点:
(1) 需要设置一个终点
(2) 需要确定物体到终点的距离
(3) 运动和距离是成正比的
两者的不同在于 “运动和距离成正比的” 这一点的实现方式不一样。
在缓动动画中,跟距离成正比的是 “速度”。物体离终点越远,速度就越快。当物体接近终点时,它就几乎停下来了。
在弹性动画中,跟距离成正比的是 “加速度”。物体离终点越远,加速度越大。刚开始时,由于加速度的影响,速度会快速增大;当物体接近终点时,加速度变得很小,但是它还在加速。由于加速度的影响,物体会越过终点。然后随着近距离的变大,反向加速度也随之变大,就会把物体拉回来。物体在终点附近来回反弹一会儿,最终在摩擦力的作用下停止。
伪代码:
```js
ax = (targetX - object.x) * spring
ay = (targetY - object.y) * spring
vx += ax
vy += ay
vx *= friction
vy *= friction
object.x += vx
object.y += vy
```
例:悠悠球效果
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>绳球运动</title>
<script src="../ball.js"></script>
<script src="../tool.js"></script>
</head>
<body>
<canvas id="canvas" width="480" height="300" style="border: 1px solid gray; display: block; margin: 0 auto;"></canvas>
<script>
window.onload = function () {
let cnv = document.getElementById('canvas')
let cxt = cnv.getContext('2d')
let ball = new Ball(cnv.width / 2, cnv.height / 2)
let mouse = tools.getMouse(cnv)
let targetX = cnv.width / 2
let spring = 0.02
let vx = 0
let vy = 0
let friction = 0.95;
(function frame () {
window.requestAnimationFrame(frame)
cxt.clearRect(0, 0, cnv.width, cnv.height)
// 加入弹性动画
let ax = (mouse.x - ball.x) * spring
let ay = (mouse.y - ball.y) * spring
vx += ax
vy += ay
vx *= friction
vy *= friction
ball.x += vx
ball.y += vy
ball.fill(cxt)
// 将鼠标以及小球中心连成一条直线
cxt.beginPath()
cxt.moveTo(ball.x, ball.y)
cxt.lineTo(mouse.x, mouse.y)
cxt.stroke()
cxt.closePath()
})()
}
</script>
</body>
</html>
```
![](https://img.kancloud.cn/64/28/6428d1d5b38edbbc1f0c88894870e1c3_641x405.gif =300x)