canvas實現粒子背景特效思路總結
效果

源碼
html
點擊查看代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>粒子背景</title>
<style>
canvas {
margin: 50px auto;
display: block;
box-shadow: 0 0 10px rgb(0 0 0 / 50%);
}
</style>
</head>
<body>
<canvas width="800" height="500"></canvas>
<script src="./js/粒子背景.js"></script>
<script>
// 粒子集合
let particles = []
// 粒子數量
const particleCount = 150
// 鼠標位置對象
let mouseOffset = null
// 粒子畫線的最小距離
const minDistance = 100
// 邊界碰撞檢測
const collideDetect = true
// 是否有鼠標交互
const hasInteractMouse = true
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
createParticles()
draw()
handleCanvasEvent()
function draw() {
// 清空畫布
canvas.width = canvas.width
drawParticles(ctx)
ctx.lineWidth = 0.1
drawLines(ctx)
requestAnimationFrame(draw)
}
// 創建粒子對象
function createParticles() {
for (let index = 0; index < particleCount; index++) {
particles.push(
new Particle(
getRandomRange(0, canvas.width, canvas.height),
getRandomRange(0, canvas.height)
)
)
}
}
// 將粒子對象畫到畫布上
function drawParticles(ctx) {
ctx.beginPath()
for (let index = 0; index < particles.length; index++) {
const particle = particles[index]
particle.draw(ctx)
// 沒有開啟邊界碰撞檢測,越界的粒子,更新位置並保證其新位置不越界
if (!collideDetect) {
if (
particle.x > canvas.width ||
particle.x < 0 ||
particle.y > canvas.height ||
particle.y < 0
) {
// 粒子直徑
const diameter = particle.radius * 2
particle.updateCoordinate(
getRandomRange(diameter, canvas.width - diameter),
getRandomRange(diameter, canvas.height - diameter)
)
}
} else {
handleCollide(particle)
}
particle.x += particle.speedX
particle.y += particle.speedY
}
ctx.fill()
}
// 繪制粒子連線
function drawLines(ctx) {
ctx.beginPath()
let arr = [...particles]
mouseOffset && (arr = [mouseOffset].concat(arr))
for (let index = 0; index < particles.length; index++) {
const particle = particles[index]
for (let j = arr.length - 1; j >= 0; j--) {
const particle2 = arr[j]
if (particle === particle2) {
continue
}
const distance = calDistance(
particle.x,
particle2.x,
particle.y,
particle2.y
)
if (distance < minDistance) {
// 如果是鼠標,則讓粒子向鼠標的位置移動,距離-10 保證粒子與鼠標之間的最小間距
if (particle2 === mouseOffset && distance > minDistance - 10) {
const xc = particle.x - particle2.x
const yc = particle.y - particle2.y
// 0.03 向鼠標坐標移動的速率
particle.x -= xc * 0.03
particle.y -= yc * 0.03
}
ctx.moveTo(particle.x, particle.y)
ctx.lineTo(particle2.x, particle2.y)
}
}
// 去掉重復比較
arr.splice(arr.indexOf(particle), 1)
}
ctx.stroke()
}
// 粒子邊界碰撞檢測
function handleCollide(particle) {
if (
(particle.speedX && particle.x + particle.radius > canvas.width) ||
(particle.speedX < 0 && particle.x < particle.radius)
) {
particle.speedX *= -1
}
if (
(particle.speedY > 0 &&
particle.y + particle.radius >= canvas.height) ||
(particle.speedY < 0 && particle.y <= particle.radius)
) {
particle.speedY *= -1
}
}
// 處理canvas的事件
function handleCanvasEvent() {
if (hasInteractMouse) {
canvas.onmousemove = function (e) {
if (!mouseOffset) {
mouseOffset = {}
}
mouseOffset.x = e.offsetX
mouseOffset.y = e.offsetY
}
}
}
function calDistance(x1, x2, y1, y2) {
return Math.hypot(x1 - x2, y1 - y2)
}
function getRandomRange(min, max) {
return Math.random() * (max - min) + min
}
</script>
</body>
</html>
粒子背景.js
點擊查看代碼
class Particle {
constructor(x, y) {
this.x = x
this.y = y
this.radius = 0.5
this.speedX = this._getRandomRange(-1, 1)
this.speedY = this._getRandomRange(-1, 1)
}
draw(ctx) {
ctx.moveTo(this.x, this.y)
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI)
}
updateCoordinate(x, y) {
this.x = x
this.y = y
return this
}
_getRandomRange(min, max) {
return Math.random() * (max - min) + min
}
}
主要思路
現象
- 粒子之間、鼠標與粒子之間,小於一定的值會連線
- 粒子的移動是隨機的
- 粒子向鼠標移動時,有一個加速的過程
思路梳理
-
抽象圓形粒子類
- 坐標,x,y。由外部傳入
- 移速,x,y。自己內部隨機定義
- 半徑。內部定義
-
准備粒子,畫布上一直只有定量的粒子
- 使用一個數組,承載創建的定量的粒子
-
畫布有鼠標移動事件的監聽
- 儲存鼠標的位置,用來判斷粒子與鼠標位置間是否應該連線
-
批量繪制粒子
- 繪制粒子時對粒子進行邊界碰撞檢測。或者到邊界后更新粒子的位置(確保其不越界)
-
批量繪制粒子間、鼠標和粒子間的連線
-
如果粒子間的球心距離小於限定的連線距離,則在這兩個粒子中間連線
-
如果粒子和鼠標的距離小於限定的連線距離,則在鼠標和粒子中間連線
-
連線前,如果粒子與鼠標位置間的距離小於一定間距,粒子向鼠標位置移動(有一個粒子圍繞着鼠標的效果)
-
移動的距離,有正、負。用鼠標的坐標和粒子的坐標做對應方向上的加減運算,再乘以一個值(表示移動速率,體現在粒子向鼠標移動的效果上)
-
計算示例實現鼠標交互的重點
-
// 如果粒子本就在鼠標附近,不用再做移動(一定間距的限制,有一個粒子圍繞鼠標但不挨着鼠標的效果) if (粒子與鼠標間的距離 < 連線限定距離 && 粒子與鼠標間的距離 > 一定間距) { // xDistance 正負不定 const xDistance = 粒子坐標x - 鼠標坐標.x // yDistance 正負不定 const yDistance = 粒子坐標y - 鼠標坐標.y const rate = 0.03 // 無論xDistance、yDistance正負如何,這里都要保證是粒子在向鼠標移動 粒子坐標.x -= xDistance * rate 粒子坐標.y -= yDistance * rate }
-
-
-