100
实现原理
1. Canvas基础设置
首先获取Canvas元素和其2D渲染上下文,这是所有绘图操作的基础:
const canvas = document.getElementById('particleCanvas');
const ctx = canvas.getContext('2d');
2. 粒子对象设计
每个粒子是一个独立对象,包含位置、速度、大小和颜色等属性:
function Particle(x, y) {
this.x = x;
this.y = y;
this.size = Math.random() * 5 + 1;
this.speedX = Math.random() * 3 - 1.5;
this.speedY = Math.random() * 3 - 1.5;
this.color = `hsl(${Math.random() * 360}, 50%, 50%)`;
}
3. 粒子更新与绘制
每个粒子需要更新位置并绘制自身:
Particle.prototype.update = function() {
this.x += this.speedX;
this.y += this.speedY;
// 边界检测
if (this.x > canvas.width || this.x canvas.height || this.y < 0) {
this.speedY = -this.speedY;
}
};
Particle.prototype.draw = function() {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
};
4. 动画循环实现
使用requestAnimationFrame实现平滑动画:
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 更新并绘制所有粒子
for (let i = 0; i < particles.length; i++) {
particles[i].update();
particles[i].draw();
}
// 连接相近的粒子
connectParticles();
requestAnimationFrame(animate);
}
animate();
5. 粒子间连线效果
当粒子距离较近时,绘制连线形成网状效果:
function connectParticles() {
for (let i = 0; i < particles.length; i++) {
for (let j = i; j < particles.length; j++) {
const dx = particles[i].x - particles[j].x;
const dy = particles[i].y - particles[j].y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100) {
ctx.beginPath();
ctx.strokeStyle = `rgba(255, 255, 255, ${1 - distance/100})`;
ctx.lineWidth = 0.5;
ctx.moveTo(particles[i].x, particles[i].y);
ctx.lineTo(particles[j].x, particles[j].y);
ctx.stroke();
}
}
}
}
6. 鼠标交互实现
添加鼠标移动事件,使粒子对鼠标位置产生反应:
let mouseX = null;
let mouseY = null;
canvas.addEventListener('mousemove', (event) => {
const rect = canvas.getBoundingClientRect();
mouseX = event.clientX - rect.left;
mouseY = event.clientY - rect.top;
});
canvas.addEventListener('mouseout', () => {
mouseX = null;
mouseY = null;
});
完整代码实现
下面是完整的JavaScript实现代码:
// 初始化Canvas
const canvas = document.getElementById('particleCanvas');
const ctx = canvas.getContext('2d');
canvas.width = 800;
canvas.height = 500;
// 调整Canvas大小为窗口大小
function resizeCanvas() {
canvas.width = window.innerWidth * 0.8;
canvas.height = window.innerHeight * 0.7;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// 粒子构造函数
function Particle(x, y) {
this.x = x;
this.y = y;
this.size = Math.random() * 5 + 1;
this.speedX = Math.random() * 3 - 1.5;
this.speedY = Math.random() * 3 - 1.5;
this.color = `hsl(${Math.random() * 360}, 50%, 50%)`;
}
// 粒子更新方法
Particle.prototype.update = function() {
this.x += this.speedX;
this.y += this.speedY;
// 边界检测与反弹
if (this.x > canvas.width || this.x canvas.height || this.y < 0) {
this.speedY = -this.speedY;
}
// 鼠标交互
if (mouseX !== null && mouseY !== null) {
const dx = this.x - mouseX;
const dy = this.y - mouseY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100) {
// 鼠标附近的粒子受到排斥力
this.speedX += dx / distance * 0.1;
this.speedY += dy / distance * 0.1;
}
}
};
// 粒子绘制方法
Particle.prototype.draw = function() {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
};
// 初始化粒子数组
let particles = [];
function initParticles(count) {
particles = [];
for (let i = 0; i < count; i++) {
const x = Math.random() * canvas.width;
const y = Math.random() * canvas.height;
particles.push(new Particle(x, y));
}
}
// 连接相近的粒子
function connectParticles() {
for (let i = 0; i < particles.length; i++) {
for (let j = i; j < particles.length; j++) {
const dx = particles[i].x - particles[j].x;
const dy = particles[i].y - particles[j].y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance {
const rect = canvas.getBoundingClientRect();
mouseX = event.clientX - rect.left;
mouseY = event.clientY - rect.top;
});
canvas.addEventListener('mouseout', () => {
mouseX = null;
mouseY = null;
});
// 动画循环
function animate() {
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i {
const count = document.getElementById('particleCount').value;
initParticles(count);
});
document.getElementById('particleCount').addEventListener('input', (e) => {
document.getElementById('countDisplay').textContent = e.target.value;
});
性能优化建议
- 使用离屏Canvas进行预渲染静态元素
- 减少不必要的粒子间距离计算(使用空间分割算法)
- 合理控制粒子数量和连线距离阈值
- 使用requestAnimationFrame而不是setInterval
- 在页面不可见时暂停动画
扩展应用
此技术可以扩展应用于:
- 网站背景特效
- 数据可视化中的节点关系图
- 游戏中的特效系统
- 交互式艺术展示
// 完整的JavaScript实现代码将在这里执行
// 初始化Canvas
const canvas = document.getElementById(‘particleCanvas’);
const ctx = canvas.getContext(‘2d’);
canvas.width = 800;
canvas.height = 500;
// 调整Canvas大小为窗口大小
function resizeCanvas() {
canvas.width = window.innerWidth * 0.8;
canvas.height = window.innerHeight * 0.7;
}
window.addEventListener(‘resize’, resizeCanvas);
resizeCanvas();
// 粒子构造函数
function Particle(x, y) {
this.x = x;
this.y = y;
this.size = Math.random() * 5 + 1;
this.speedX = Math.random() * 3 – 1.5;
this.speedY = Math.random() * 3 – 1.5;
this.color = `hsl(${Math.random() * 360}, 50%, 50%)`;
}
// 粒子更新方法
Particle.prototype.update = function() {
this.x += this.speedX;
this.y += this.speedY;
// 边界检测与反弹
if (this.x > canvas.width || this.x canvas.height || this.y < 0) {
this.speedY = -this.speedY;
}
// 鼠标交互
if (mouseX !== null && mouseY !== null) {
const dx = this.x – mouseX;
const dy = this.y – mouseY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100) {
// 鼠标附近的粒子受到排斥力
this.speedX += dx / distance * 0.1;
this.speedY += dy / distance * 0.1;
}
}
};
// 粒子绘制方法
Particle.prototype.draw = function() {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
};
// 初始化粒子数组
let particles = [];
function initParticles(count) {
particles = [];
for (let i = 0; i < count; i++) {
const x = Math.random() * canvas.width;
const y = Math.random() * canvas.height;
particles.push(new Particle(x, y));
}
}
// 连接相近的粒子
function connectParticles() {
for (let i = 0; i < particles.length; i++) {
for (let j = i; j < particles.length; j++) {
const dx = particles[i].x – particles[j].x;
const dy = particles[i].y – particles[j].y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance {
const rect = canvas.getBoundingClientRect();
mouseX = event.clientX – rect.left;
mouseY = event.clientY – rect.top;
});
canvas.addEventListener(‘mouseout’, () => {
mouseX = null;
mouseY = null;
});
// 动画循环
function animate() {
ctx.fillStyle = ‘rgba(0, 0, 0, 0.05)’;
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i {
const count = document.getElementById(‘particleCount’).value;
initParticles(count);
});
document.getElementById(‘particleCount’).addEventListener(‘input’, (e) => {
document.getElementById(‘countDisplay’).textContent = e.target.value;
});

