[TOC]
所謂邊界檢測簡單來說就是給運動物體限定一個范圍,從而實現某些動畫效果。在 Canvas 中,大部分情況下都會把物體運動范圍設置為整個畫布,有時候也可以是畫布的一部分。

如上圖,假設有一個小球,其中心坐標為(x,y),那么此時的邊界檢測代碼為:
```js
if (ball.x < ball.radius) {
// "碰到" 左邊界時做什么
} else if (ball.x > cnv.width - ball.radius) {
// "碰到" 右邊界時做什么
} else if (ball.y < ball.radius) {
// "碰到" 上邊界時做什么
} else if (ball.y > cnv.height - ball.radius) {
// "碰到" 下邊界時做什么
}
```
接下來從以下四個方向介紹邊界檢測
- 邊界限制
- 邊界生成
- 邊界環繞
- 邊界反彈
## 邊界限制
邊界限制指的是通過邊界檢測的辦法來限制物體的運動范圍,使其無法超出這個運動范圍,而只在限制的范圍內運動。
```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="./tool.js"></script>
<script src="./ball.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)
ball.fill(cxt)
let key = tools.getKey()
// 添加鍵盤事件
window.addEventListener('keydown', function (e) {
cxt.clearRect(0, 0, cnv.width, cnv.height)
// 根據 key.direction 的值,判斷物體移動方向
switch (key.direction) {
case 'up':
ball.y -= 3
checkBorder()
ball.fill(cxt)
break
case 'down':
ball.y += 3
checkBorder()
ball.fill(cxt)
break
case 'left':
ball.x -= 3
checkBorder()
ball.fill(cxt)
break
case 'right':
ball.x += 3
checkBorder()
ball.fill(cxt)
break
default:
checkBorder()
ball.fill(cxt)
}
}, false)
// 定義邊界檢測函數
function checkBorder () {
if (ball.x < ball.radius) {
// "碰到" 左邊界時做什么
ball.x = ball.radius
} else if (ball.x > cnv.width - ball.radius) {
// "碰到" 右邊界時做什么
ball.x = cnv.width - ball.radius
} else if (ball.y < ball.radius) {
// "碰到" 上邊界時做什么
ball.y = ball.radius
} else if (ball.y > cnv.height - ball.radius) {
// "碰到" 下邊界時做什么
ball.y = cnv.height - ball.radius
}
}
}
</script>
</body>
</html>
```
## 邊界環繞
邊界環繞指的是當物體從一個邊界消失后,它就會從對立的邊界重新出現,從而形成一種環繞效果。
語法:
```js
if (ball.x < -ball.radius) {
// 小球 "完全超出" 左邊界時
} else if (ball.x > cnv.width + ball.radius) {
// 小球 "完全超出" 右邊界時
} else if (ball.y < -ball.radius) {
// 小球 "完全超出" 上邊界時
} else if (ball.y > cnv.height + ball.raidus) {
// 小球 "完全超出" 下邊界時
}
```
注意這里的 “完全超出” 的含義,當小球完全超出邊界時,此時小球在畫布外面,如下圖所示:

## 邊界生成
邊界生成,指的是物體完全超出邊界后,在最開始的位置重新生成。這種技巧可用于創建噴泉及各種粒子特效。例如在噴泉效果中,水滴不斷地飛濺出來,飛出邊界后又重新加入到水流的源頭。這樣物體的數量就是固定不變的,不用擔心因物體數量過多而影響瀏覽器性能。
語法:
```js
if (ball.x < -ball.radius ||
ball.x > cnv.width + ball.radius ||
ball.y < -ball.radius ||
ball.y > cnv.height + ball.radius
// ...
)
```
注意這里用的是 "或" 運算。
```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="./tool.js"></script>
<script src="./ball.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')
// 定義一個用來存放小球的數組 balls
let balls = []
// n 表示小球數量
let n = 50
// 生成 n 個小球,其中小球的 color、vx、vy 都是隨機值
for (let i = 0; i < n; i++) {
// 球心坐標為 Canvas 中心,color 為隨機顏色值
let ball = new Ball(cnv.width / 2, cnv.height / 2, 5, tools.getRandomColor())
// ball.vx 和 ball.vy 取值都是 -1 ~ 1 之間的任意數
ball.vx = Math.random() * 2 - 1
ball.vy = Math.random() * 2 - 1
balls.push(ball)
};
(function frame () {
window.requestAnimationFrame(frame)
cxt.clearRect(0, 0, cnv.width, cnv.height)
balls.forEach(function (ball) {
// 邊界檢測
if (ball.x < -ball.radius ||
ball.x > cnv.width + ball.radius ||
ball.y < -ball.radius ||
ball.y > cnv.height + ball.radius
) {
ball.x = cnv.width / 2
ball.y = cnv.height / 2
ball.vx = Math.random() * 2 - 1
ball.vy = Math.random() * 2 - 1
}
ball.fill(cxt)
ball.x += ball.vx
ball.y += ball.vy
})
})()
}
</script>
</body>
</html>
```

## 邊界反彈
邊界反彈,指的是物體觸碰到邊界之后就會反彈回來。偽代碼如下:
```js
if (ball.x < ball.radius) {
// 碰到左邊界
ball.x = ball.radius
vx = -vx
} else if (ball.x > canvas.width - ball.radius) {
// 碰到右邊界
ball.x = canvas.width - ball.radius
vx = -vx
} else if (ball.y < ball.radius) {
// 碰到上邊界
ball.y = ball.radius
vy = -vy
} else if (ball.y > canvas.height - ball.radius) {
ball.y = canvas.height - ball.radius
vy = -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="./tool.js"></script>
<script src="./ball.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')
// 定義一個用來存放小球的數組 balls
const balls = []
const n = 10 // 小球數量
// 生成 n 個小球,其中小球的 color、vx、vy 都是隨機值
for (let i = 0; i < n; i++) {
let ball = new Ball(cnv.width / 2, cnv.height / 2, 8, tools.getRandomColor())
ball.vx = (Math.random() * 2 - 1) * 3
ball.vy = (Math.random() * 2 - 1) * 3
balls.push(ball)
};
(function frame () {
window.requestAnimationFrame(frame)
cxt.clearRect(0, 0, cnv.width, cnv.height)
balls.forEach(function (ball) {
ball.x += ball.vx
ball.y += ball.vy
if (ball.x < ball.radius) {
// 碰到左邊界
ball.x = ball.radius
ball.vx = -ball.vx
} else if (ball.x > canvas.width - ball.radius) {
// 碰到右邊界
ball.x = canvas.width - ball.radius
ball.vx = -ball.vx
} else if (ball.y < ball.radius) {
// 碰到上邊界
ball.y = ball.radius
ball.vy = -ball.vy
} else if (ball.y > canvas.height - ball.radius) {
ball.y = canvas.height - ball.radius
ball.vy = -ball.vy
}
ball.fill(cxt)
})
})()
}
</script>
</body>
</html>
```

可以看到,對于多物體運動,一般情況下都是采取以下三個步驟進行處理:
1. 定義一個數組來存放多個物體
2. 使用 for 循環生成單個物體,然后添加到數組中
3. 在動畫循環中,使用 forEach() 方法遍歷數組,從而處理單個物體