[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() 方法遍歷數組,從而處理單個物體
- 序言 & 更新日志
- H5
- Canvas
- 序言
- Part1-直線、矩形、多邊形
- Part2-曲線圖形
- Part3-線條操作
- Part4-文本操作
- Part5-圖像操作
- Part6-變形操作
- Part7-像素操作
- Part8-漸變與陰影
- Part9-路徑與狀態
- Part10-物理動畫
- Part11-邊界檢測
- Part12-碰撞檢測
- Part13-用戶交互
- Part14-高級動畫
- CSS
- SCSS
- codePen
- 速查表
- 面試題
- 《CSS Secrets》
- SVG
- 移動端適配
- 濾鏡(filter)的使用
- JS
- 基礎概念
- 作用域、作用域鏈、閉包
- this
- 原型與繼承
- 數組、字符串、Map、Set方法整理
- 垃圾回收機制
- DOM
- BOM
- 事件循環
- 嚴格模式
- 正則表達式
- ES6部分
- 設計模式
- AJAX
- 模塊化
- 讀冴羽博客筆記
- 第一部分總結-深入JS系列
- 第二部分總結-專題系列
- 第三部分總結-ES6系列
- 網絡請求中的數據類型
- 事件
- 表單
- 函數式編程
- Tips
- JS-Coding
- Framework
- Vue
- 書寫規范
- 基礎
- vue-router & vuex
- 深入淺出 Vue
- 響應式原理及其他
- new Vue 發生了什么
- 組件化
- 編譯流程
- Vue Router
- Vuex
- 前端路由的簡單實現
- React
- 基礎
- 書寫規范
- Redux & react-router
- immutable.js
- CSS 管理
- React 16新特性-Fiber 與 Hook
- 《深入淺出React和Redux》筆記
- 前半部分
- 后半部分
- react-transition-group
- Vue 與 React 的對比
- 工程化與架構
- Hybird
- React Native
- 新手上路
- 內置組件
- 常用插件
- 問題記錄
- Echarts
- 基礎
- Electron
- 序言
- 配置 Electron 開發環境 & 基礎概念
- React + TypeScript 仿 Antd
- TypeScript 基礎
- React + ts
- 樣式設計
- 組件測試
- 圖標解決方案
- Storybook 的使用
- Input 組件
- 在線 mock server
- 打包與發布
- Algorithm
- 排序算法及常見問題
- 劍指 offer
- 動態規劃
- DataStruct
- 概述
- 樹
- 鏈表
- Network
- Performance
- Webpack
- PWA
- Browser
- Safety
- 微信小程序
- mpvue 課程實戰記錄
- 服務器
- 操作系統基礎知識
- Linux
- Nginx
- redis
- node.js
- 基礎及原生模塊
- express框架
- node.js操作數據庫
- 《深入淺出 node.js》筆記
- 前半部分
- 后半部分
- 數據庫
- SQL
- 面試題收集
- 智力題
- 面試題精選1
- 面試題精選2
- 問答篇
- 2025面試題收集
- Other
- markdown 書寫
- Git
- LaTex 常用命令
- Bugs