<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas Particle Constellation</title>
<style>
body {
margin: 0;
overflow: hidden; /* Hide scrollbars */
background-color: #0d0d21; /* Dark navy background */
}
canvas {
display: block; /* Remove default margin */
}
</style>
</head>
<body>
<canvas id="constellationCanvas"></canvas>
<script>
const canvas = document.getElementById('constellationCanvas');
const ctx = canvas.getContext('2d');
// Set canvas to full screen
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// --- Configuration ---
const config = {
particleColor: `rgba(0, 191, 255, 0.7)`, // Deep sky blue with some transparency
lineColor: `rgba(0, 191, 255, 0.3)`,
particleAmount: 100,
defaultSpeed: 0.5,
defaultRadius: 3,
linkRadius: 150, // Max distance to draw a line
};
// --- Mouse Interaction ---
let mouse = {
x: null,
y: null,
radius: config.linkRadius,
};
window.addEventListener('mousemove', (event) => {
mouse.x = event.x;
mouse.y = event.y;
});
window.addEventListener('mouseout', () => {
mouse.x = null;
mouse.y = null;
});
// --- Particle Class ---
class Particle {
constructor(x, y, radius, speed) {
this.x = x;
this.y = y;
this.radius = radius;
// Randomize velocity for movement in any direction
this.vx = (Math.random() - 0.5) * speed;
this.vy = (Math.random() - 0.5) * speed;
}
// Draw the particle on the canvas
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = config.particleColor;
ctx.fill();
}
// Update particle position and handle bouncing off edges
update() {
// Bounce off horizontal walls
if (this.x + this.radius > canvas.width || this.x - this.radius < 0) {
this.vx = -this.vx;
}
// Bounce off vertical walls
if (this.y + this.radius > canvas.height || this.y - this.radius < 0) {
this.vy = -this.vy;
}
this.x += this.vx;
this.y += this.vy;
}
}
let particles = [ ];
// --- Initialization ---
function init() {
particles = [ ];
for (let i = 0; i < config.particleAmount; i++) {
const x = Math.random() * canvas.width;
const y = Math.random() * canvas.height;
particles.push(new Particle(x, y, config.defaultRadius, config.defaultSpeed));
}
}
// --- Line Drawing Logic ---
function drawLines() {
let allPoints = [...particles, mouse]; // Include mouse as a point to connect to
for (let i = 0; i < allPoints.length; i++) {
for (let j = i + 1; j < allPoints.length; j++) {
// Skip if the mouse point is not on the canvas
if (allPoints[i].x === null || allPoints[j].x === null) continue;
const dx = allPoints[i].x - allPoints[j].x;
const dy = allPoints[i].y - allPoints[j].y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < config.linkRadius) {
ctx.beginPath();
ctx.moveTo(allPoints[i].x, allPoints[i].y);
ctx.lineTo(allPoints[j].x, allPoints[j].y);
// Make lines more transparent the further they are
ctx.strokeStyle = `rgba(0, 191, 255, ${1 - distance / config.linkRadius})`;
ctx.lineWidth = 1;
ctx.stroke();
ctx.closePath();
}
}
}
}
// --- Animation Loop ---
function animate() {
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Update and draw each particle
for (const particle of particles) {
particle.update();
particle.draw();
}
// Draw the connecting lines
drawLines();
// Request the next frame
requestAnimationFrame(animate);
}
// --- Start ---
init();
animate();
// --- Handle window resizing ---
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
mouse.radius = config.linkRadius;
init(); // Re-initialize particles for the new size
});
</script>
</body>
</html>