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;
});