緩動動畫和彈性動畫都是 “把一個物體從已有位置移動到目標位置” 的方法。緩動動畫指的是物體滑動到目標點就停下來了,而彈性動畫指的是物體來回地反彈一會兒,最終停在目標點的運動。
## 緩動動畫
緩動動畫指的是帶有一定緩沖效果的動畫。在動畫過程中,物體在某一段時間會 “漸進加速” 或 “漸進減速”,從而讓物體運動看起來更為自然和逼真。
在 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>
```
這個緩動動畫的效果就是小球運動到終點的速度有一個由快到慢的過程,即隨著距離的不斷減小小球的運動速度也不斷減小。緩動動畫看上去和摩擦力的效果很像,但是兩者也有本質上的區別:在摩擦力動畫中,每一幀的當前速度等于上一幀速度乘以摩擦系數,其速度是按照固定比例改變的;而緩動動畫中,每一幀的當前速度等于距離乘以緩動系數,其速度并不是按照固定比例改變的。

### 例:任意方向的緩動動畫
```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>
```

### 例:緩動動畫應用
緩動動畫不僅能用于物體的運動,其也可以用于物體的各種屬性,包括大小、顏色、透明度及旋轉等。
不管緩動動畫應用于什么方面,其實現思路都是一樣的,即以下兩個步驟:
(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>
```

## 彈性動畫
彈性動畫和緩動動畫是非常相似的,它們的實現都是 “把一個物體從一個位置移動到另一個位置” 的動畫效果。不過這兩者也有明顯的區別:在緩動動畫中,物體滑動到終點就停下來了,而在彈性動畫動畫中,物體滑動到終點后還會來回反彈一會兒,直至停止。
從技術上說,緩動動畫和彈性動畫有以下幾個共同點:
(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>
```
