Particle Network
マウスに反応するパーティクル背景。
Canvasで実装した粒子が線で繋がるインタラクティブエフェクトです。
Demo
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Particle Network</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<canvas id="particleCanvas"></canvas>
<div class="content">
<h1>Particle Network</h1>
<p>マウスを動かすと粒子が反応します</p>
</div>
<script src="script.js"></script>
</body>
</html>
style.css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
margin: 0;
overflow: hidden;
background: #0a0a0a;
font-family: 'Arial', sans-serif;
}
#particleCanvas {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.content {
position: relative;
z-index: 2;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
padding: 20px;
pointer-events: none;
}
.content h1 {
font-size: 3rem;
font-weight: bold;
color: #fff;
margin-bottom: 1rem;
text-shadow: 0 0 20px rgba(99, 102, 241, 0.8);
}
.content p {
font-size: 1.2rem;
color: rgba(255, 255, 255, 0.7);
}
@media (max-width: 768px) {
.content h1 {
font-size: 2rem;
}
.content p {
font-size: 1rem;
}
}
script.js
const canvas = document.getElementById('particleCanvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let particlesArray = [];
let mouse = {
x: null,
y: null,
radius: 150
};
window.addEventListener('mousemove', (e) => {
mouse.x = e.x;
mouse.y = e.y;
});
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
init();
});
class Particle {
constructor(x, y) {
this.x = x;
this.y = y;
this.size = Math.random() * 3 + 1;
this.baseX = this.x;
this.baseY = this.y;
this.density = (Math.random() * 30) + 1;
this.color = `hsl(${Math.random() * 60 + 200}, 100%, 70%)`;
}
draw() {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
}
update() {
let dx = mouse.x - this.x;
let dy = mouse.y - this.y;
let distance = Math.sqrt(dx * dx + dy * dy);
let forceDirectionX = dx / distance;
let forceDirectionY = dy / distance;
let maxDistance = mouse.radius;
let force = (maxDistance - distance) / maxDistance;
let directionX = forceDirectionX * force * this.density;
let directionY = forceDirectionY * force * this.density;
if (distance < mouse.radius) {
this.x -= directionX;
this.y -= directionY;
} else {
if (this.x !== this.baseX) {
let dx = this.x - this.baseX;
this.x -= dx / 10;
}
if (this.y !== this.baseY) {
let dy = this.y - this.baseY;
this.y -= dy / 10;
}
}
}
}
function init() {
particlesArray = [];
let numberOfParticles = (canvas.width * canvas.height) / 9000;
for (let i = 0; i < numberOfParticles; i++) {
let x = Math.random() * canvas.width;
let y = Math.random() * canvas.height;
particlesArray.push(new Particle(x, y));
}
}
function connect() {
for (let a = 0; a < particlesArray.length; a++) {
for (let b = a; b < particlesArray.length; b++) {
let dx = particlesArray[a].x - particlesArray[b].x;
let dy = particlesArray[a].y - particlesArray[b].y;
let distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100) {
let opacity = 1 - (distance / 100);
ctx.strokeStyle = `rgba(99, 102, 241, ${opacity})`;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(particlesArray[a].x, particlesArray[a].y);
ctx.lineTo(particlesArray[b].x, particlesArray[b].y);
ctx.stroke();
}
}
}
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < particlesArray.length; i++) {
particlesArray[i].draw();
particlesArray[i].update();
}
connect();
requestAnimationFrame(animate);
}
init();
animate();
Download
すべてのファイルをZIP形式でダウンロードできます。