Recently, I had some free time and created a fun interactive background effect for websites. The main idea is to use the <canvas>
element to draw a large number of freely moving particles in the background. When the mouse gets close to a particle, it interacts smoothly with the cursor, creating a silky and dynamic experience.
Dynamic Particle Effects
- Instead of disappearing when reaching the edge of the screen, particles reappear from the opposite side, creating a seamless wrap-around effect.
- Each particle has its own random initial position, speed, and size.
- When particles are close to each other, semi-transparent lines are automatically drawn between them, forming a web-like visual structure.
The entire dynamic background is implemented purely with JavaScript—lightweight, efficient, and easy to integrate into almost any webpage.
Demo Link Demo Resource
HTML代码
<!DOCTYPE html><html lang=”zh”>
<head>
<meta charset=”UTF-8″>
<meta name=”viewport” content=”width=device-width, initial-scale=1.0″>
<title>粒子背景效果</title>
<link rel=”stylesheet” href=”style.css”>
</head>
<body>
<!– 粒子背景 –>
<div id=”particles-container”></div>
<!– 粒子脚本 –>
<script src=”script.js”></script>
</body>
</html>
CSS代码
/* 基础样式 */:root {
–bg-color: #222;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: var(–bg-color);
overflow: hidden;
width: 100vw;
height: 100vh;
}
/* 粒子背景 */
#particles-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
}
JS代码
document.addEventListener(‘DOMContentLoaded’, function () {const particlesContainer = document.getElementById(‘particles-container’);
const particles = [];
const particleCount = 100;
const colors = [‘#aaa’, ‘#bbb’, ‘#ccc’, ‘#ddd’];
const mousePosition = { x: null, y: null };
document.addEventListener(‘mousemove’, function (event) {
mousePosition.x = event.clientX;
mousePosition.y = event.clientY;
});
document.addEventListener(‘mouseleave’, function () {
mousePosition.x = null;
mousePosition.y = null;
});
class Particle {
constructor(canvas) {
this.init(canvas);
}
init(canvas) {
this.x = Math.random() * window.innerWidth;
this.y = Math.random() * window.innerHeight;
this.size = Math.random() * 3 + 1;
this.baseSpeedX = (Math.random() – 0.5) * 0.5;
this.baseSpeedY = (Math.random() – 0.5) * 0.5;
this.speedX = this.baseSpeedX;
this.speedY = this.baseSpeedY;
this.color = colors[Math.floor(Math.random() * colors.length)];
this.canvas = canvas;
this.ctx = canvas.getContext(‘2d’);
this.recovered = true;
}
update() {
this.x += this.speedX;
this.y += this.speedY;
if (this.x < 0) this.x = window.innerWidth;
else if (this.x > window.innerWidth) this.x = 0;
if (this.y < 0) this.y = window.innerHeight;
else if (this.y > window.innerHeight) this.y = 0;
let influenced = false;
if (mousePosition.x && mousePosition.y) {
const dx = mousePosition.x – this.x;
const dy = mousePosition.y – this.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100) {
const angle = Math.atan2(dy, dx);
const force = (100 – distance) / 1500;
this.speedX -= Math.cos(angle) * force;
this.speedY -= Math.sin(angle) * force;
influenced = true;
this.recovered = false;
}
}
if (!influenced && !this.recovered) {
const dSpeedX = this.baseSpeedX – this.speedX;
const dSpeedY = this.baseSpeedY – this.speedY;
this.speedX += dSpeedX * 0.01;
this.speedY += dSpeedY * 0.01;
if (Math.abs(dSpeedX) < 0.01 && Math.abs(dSpeedY) < 0.01) {
this.speedX = this.baseSpeedX;
this.speedY = this.baseSpeedY;
this.recovered = true;
}
}
const maxSpeed = 2;
const currentSpeed = Math.sqrt(this.speedX ** 2 + this.speedY ** 2);
if (currentSpeed > maxSpeed) {
this.speedX = (this.speedX / currentSpeed) * maxSpeed;
this.speedY = (this.speedY / currentSpeed) * maxSpeed;
}
}
draw() {
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
this.ctx.fillStyle = this.color;
this.ctx.fill();
}
}
const canvas = document.createElement(‘canvas’);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
particlesContainer.appendChild(canvas);
const ctx = canvas.getContext(‘2d’);
for (let i = 0; i < particleCount; i++) {
particles.push(new Particle(canvas));
}
window.addEventListener(‘resize’, function () {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
particles.forEach(p => {
p.x = Math.random() * window.innerWidth;
p.y = Math.random() * window.innerHeight;
});
});
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
particles.forEach(p => {
p.update();
p.draw();
});
connectParticles();
if (particles.length < particleCount) {
for (let i = particles.length; i < particleCount; i++) {
particles.push(new Particle(canvas));
}
}
requestAnimationFrame(animate);
}
function connectParticles() {
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; 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 < 120) {
const opacity = 1 – (distance / 120);
ctx.beginPath();
ctx.strokeStyle = `rgba(200, 200, 200, ${opacity * 0.2})`;
ctx.lineWidth = 0.5;
ctx.moveTo(particles[i].x, particles[i].y);
ctx.lineTo(particles[j].x, particles[j].y);
ctx.stroke();
}
}
}
}
animate();
});
Comments(0)
So quiet here… No comments yet!