UI Components Medium
Particles System
Interactive canvas particle system with connected lines between nearby particles and mouse-driven attraction and repulsion.
Open in Lab
MCP
css javascript canvas vue svelte
Targets: TS JS HTML React Vue Svelte
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, sans-serif;
min-height: 100vh;
background: #0a0a0a;
overflow: hidden;
position: relative;
}
#particles-canvas {
position: fixed;
inset: 0;
width: 100%;
height: 100%;
display: block;
}
.particles-overlay {
position: fixed;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
pointer-events: none;
z-index: 10;
}
.particles-title {
font-size: clamp(2rem, 5vw, 3.5rem);
font-weight: 800;
letter-spacing: -0.03em;
background: linear-gradient(135deg, #e0e7ff 0%, #818cf8 50%, #6366f1 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 0.5rem;
}
.particles-subtitle {
font-size: clamp(0.875rem, 2vw, 1.125rem);
color: rgba(148, 163, 184, 0.7);
}// Interactive Particles System — canvas particles with connections and mouse interaction
(function () {
"use strict";
const canvas = document.getElementById("particles-canvas");
if (!canvas) return;
const ctx = canvas.getContext("2d");
const CONFIG = {
count: 120,
color: { r: 99, g: 102, b: 241 },
connectionDistance: 150,
particleSizeMin: 1,
particleSizeMax: 3,
speed: 0.4,
mouseRadius: 180,
mouseForce: 0.08,
};
let width, height;
const particles = [];
const mouse = { x: -9999, y: -9999, active: false };
function resize() {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
}
function createParticle() {
return {
x: Math.random() * width,
y: Math.random() * height,
vx: (Math.random() - 0.5) * CONFIG.speed * 2,
vy: (Math.random() - 0.5) * CONFIG.speed * 2,
size:
CONFIG.particleSizeMin + Math.random() * (CONFIG.particleSizeMax - CONFIG.particleSizeMin),
opacity: 0.3 + Math.random() * 0.5,
};
}
function init() {
resize();
particles.length = 0;
for (let i = 0; i < CONFIG.count; i++) {
particles.push(createParticle());
}
}
function update() {
for (let i = 0; i < particles.length; i++) {
const p = particles[i];
// Mouse interaction — repulsion
if (mouse.active) {
const dx = p.x - mouse.x;
const dy = p.y - mouse.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < CONFIG.mouseRadius && dist > 0) {
const force = (CONFIG.mouseRadius - dist) / CONFIG.mouseRadius;
p.vx += (dx / dist) * force * CONFIG.mouseForce;
p.vy += (dy / dist) * force * CONFIG.mouseForce;
}
}
// Damping
p.vx *= 0.99;
p.vy *= 0.99;
// Clamp velocity
const maxV = CONFIG.speed * 3;
p.vx = Math.max(-maxV, Math.min(maxV, p.vx));
p.vy = Math.max(-maxV, Math.min(maxV, p.vy));
p.x += p.vx;
p.y += p.vy;
// Bounce off edges
if (p.x < 0) {
p.x = 0;
p.vx *= -1;
}
if (p.x > width) {
p.x = width;
p.vx *= -1;
}
if (p.y < 0) {
p.y = 0;
p.vy *= -1;
}
if (p.y > height) {
p.y = height;
p.vy *= -1;
}
}
}
function draw() {
ctx.clearRect(0, 0, width, height);
const { r, g, b } = CONFIG.color;
// Draw connections
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const a = particles[i];
const bP = particles[j];
const dx = a.x - bP.x;
const dy = a.y - bP.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < CONFIG.connectionDistance) {
const alpha = (1 - dist / CONFIG.connectionDistance) * 0.3;
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(bP.x, bP.y);
ctx.strokeStyle = `rgba(${r}, ${g}, ${b}, ${alpha})`;
ctx.lineWidth = 0.5;
ctx.stroke();
}
}
}
// Draw particles
for (let i = 0; i < particles.length; i++) {
const p = particles[i];
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${p.opacity})`;
ctx.fill();
// Glow
ctx.beginPath();
ctx.arc(p.x, p.y, p.size * 2, 0, Math.PI * 2);
ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${p.opacity * 0.15})`;
ctx.fill();
}
// Mouse glow
if (mouse.active) {
const gradient = ctx.createRadialGradient(
mouse.x,
mouse.y,
0,
mouse.x,
mouse.y,
CONFIG.mouseRadius
);
gradient.addColorStop(0, `rgba(${r}, ${g}, ${b}, 0.08)`);
gradient.addColorStop(1, "transparent");
ctx.beginPath();
ctx.arc(mouse.x, mouse.y, CONFIG.mouseRadius, 0, Math.PI * 2);
ctx.fillStyle = gradient;
ctx.fill();
}
}
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
canvas.addEventListener("mousemove", function (e) {
mouse.x = e.clientX;
mouse.y = e.clientY;
mouse.active = true;
});
canvas.addEventListener("mouseleave", function () {
mouse.active = false;
});
window.addEventListener("resize", function () {
resize();
});
init();
loop();
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Particles System</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<canvas id="particles-canvas"></canvas>
<div class="particles-overlay">
<h1 class="particles-title">Particles System</h1>
<p class="particles-subtitle">Move your mouse to interact with the particles</p>
</div>
<script src="script.js"></script>
</body>
</html>import { useEffect, useRef, useCallback } from "react";
interface ParticlesSystemProps {
count?: number;
color?: { r: number; g: number; b: number };
connectionDistance?: number;
speed?: number;
mouseRadius?: number;
className?: string;
}
interface Particle {
x: number;
y: number;
vx: number;
vy: number;
size: number;
opacity: number;
}
export function ParticlesSystem({
count = 120,
color = { r: 99, g: 102, b: 241 },
connectionDistance = 150,
speed = 0.4,
mouseRadius = 180,
className = "",
}: ParticlesSystemProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const particlesRef = useRef<Particle[]>([]);
const mouseRef = useRef({ x: -9999, y: -9999, active: false });
const animRef = useRef<number>(0);
const sizeRef = useRef({ w: 0, h: 0 });
const createParticle = useCallback(
(w: number, h: number): Particle => ({
x: Math.random() * w,
y: Math.random() * h,
vx: (Math.random() - 0.5) * speed * 2,
vy: (Math.random() - 0.5) * speed * 2,
size: 1 + Math.random() * 2,
opacity: 0.3 + Math.random() * 0.5,
}),
[speed]
);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
function resize() {
const parent = canvas!.parentElement;
if (!parent) return;
const w = parent.clientWidth;
const h = parent.clientHeight;
canvas!.width = w;
canvas!.height = h;
sizeRef.current = { w, h };
}
function init() {
resize();
const { w, h } = sizeRef.current;
particlesRef.current = [];
for (let i = 0; i < count; i++) {
particlesRef.current.push(createParticle(w, h));
}
}
function update() {
const { w, h } = sizeRef.current;
const mouse = mouseRef.current;
const particles = particlesRef.current;
const mouseForce = 0.08;
const maxV = speed * 3;
for (const p of particles) {
if (mouse.active) {
const dx = p.x - mouse.x;
const dy = p.y - mouse.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < mouseRadius && dist > 0) {
const force = (mouseRadius - dist) / mouseRadius;
p.vx += (dx / dist) * force * mouseForce;
p.vy += (dy / dist) * force * mouseForce;
}
}
p.vx *= 0.99;
p.vy *= 0.99;
p.vx = Math.max(-maxV, Math.min(maxV, p.vx));
p.vy = Math.max(-maxV, Math.min(maxV, p.vy));
p.x += p.vx;
p.y += p.vy;
if (p.x < 0) {
p.x = 0;
p.vx *= -1;
}
if (p.x > w) {
p.x = w;
p.vx *= -1;
}
if (p.y < 0) {
p.y = 0;
p.vy *= -1;
}
if (p.y > h) {
p.y = h;
p.vy *= -1;
}
}
}
function draw() {
const { w, h } = sizeRef.current;
const particles = particlesRef.current;
const mouse = mouseRef.current;
const { r, g, b } = color;
ctx!.clearRect(0, 0, w, h);
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const a = particles[i];
const bp = particles[j];
const dx = a.x - bp.x;
const dy = a.y - bp.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < connectionDistance) {
const alpha = (1 - dist / connectionDistance) * 0.3;
ctx!.beginPath();
ctx!.moveTo(a.x, a.y);
ctx!.lineTo(bp.x, bp.y);
ctx!.strokeStyle = `rgba(${r},${g},${b},${alpha})`;
ctx!.lineWidth = 0.5;
ctx!.stroke();
}
}
}
for (const p of particles) {
ctx!.beginPath();
ctx!.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx!.fillStyle = `rgba(${r},${g},${b},${p.opacity})`;
ctx!.fill();
ctx!.beginPath();
ctx!.arc(p.x, p.y, p.size * 2, 0, Math.PI * 2);
ctx!.fillStyle = `rgba(${r},${g},${b},${p.opacity * 0.15})`;
ctx!.fill();
}
if (mouse.active) {
const gradient = ctx!.createRadialGradient(
mouse.x,
mouse.y,
0,
mouse.x,
mouse.y,
mouseRadius
);
gradient.addColorStop(0, `rgba(${r},${g},${b},0.08)`);
gradient.addColorStop(1, "transparent");
ctx!.beginPath();
ctx!.arc(mouse.x, mouse.y, mouseRadius, 0, Math.PI * 2);
ctx!.fillStyle = gradient;
ctx!.fill();
}
}
function loop() {
update();
draw();
animRef.current = requestAnimationFrame(loop);
}
const handleMouseMove = (e: MouseEvent) => {
const rect = canvas!.getBoundingClientRect();
mouseRef.current.x = e.clientX - rect.left;
mouseRef.current.y = e.clientY - rect.top;
mouseRef.current.active = true;
};
const handleMouseLeave = () => {
mouseRef.current.active = false;
};
const handleResize = () => {
resize();
};
canvas.addEventListener("mousemove", handleMouseMove);
canvas.addEventListener("mouseleave", handleMouseLeave);
window.addEventListener("resize", handleResize);
init();
loop();
return () => {
cancelAnimationFrame(animRef.current);
canvas.removeEventListener("mousemove", handleMouseMove);
canvas.removeEventListener("mouseleave", handleMouseLeave);
window.removeEventListener("resize", handleResize);
};
}, [count, color, connectionDistance, speed, mouseRadius, createParticle]);
return (
<div className={className} style={{ position: "relative", width: "100%", height: "100%" }}>
<canvas ref={canvasRef} style={{ display: "block", width: "100%", height: "100%" }} />
</div>
);
}
// Demo usage
export default function ParticlesSystemDemo() {
return (
<div
style={{
width: "100vw",
height: "100vh",
background: "#0a0a0a",
position: "relative",
fontFamily: "system-ui, -apple-system, sans-serif",
}}
>
<ParticlesSystem count={120} color={{ r: 99, g: 102, b: 241 }} connectionDistance={150} />
<div
style={{
position: "absolute",
inset: 0,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
pointerEvents: "none",
zIndex: 10,
}}
>
<h1
style={{
fontSize: "clamp(2rem, 5vw, 3.5rem)",
fontWeight: 800,
letterSpacing: "-0.03em",
background: "linear-gradient(135deg, #e0e7ff 0%, #818cf8 50%, #6366f1 100%)",
WebkitBackgroundClip: "text",
WebkitTextFillColor: "transparent",
backgroundClip: "text",
marginBottom: "0.5rem",
}}
>
Particles System
</h1>
<p style={{ fontSize: "1rem", color: "rgba(148,163,184,0.7)" }}>
Move your mouse to interact with the particles
</p>
</div>
</div>
);
}<script setup>
import { ref, onMounted, onUnmounted } from "vue";
const props = defineProps({
count: { type: Number, default: 120 },
color: { type: Object, default: () => ({ r: 99, g: 102, b: 241 }) },
connectionDistance: { type: Number, default: 150 },
speed: { type: Number, default: 0.4 },
mouseRadius: { type: Number, default: 180 },
});
const canvasRef = ref(null);
let particles = [];
let mouse = { x: -9999, y: -9999, active: false };
let animId = 0;
let sizeState = { w: 0, h: 0 };
function createParticle(w, h) {
return {
x: Math.random() * w,
y: Math.random() * h,
vx: (Math.random() - 0.5) * props.speed * 2,
vy: (Math.random() - 0.5) * props.speed * 2,
size: 1 + Math.random() * 2,
opacity: 0.3 + Math.random() * 0.5,
};
}
function resize() {
const canvas = canvasRef.value;
if (!canvas) return;
const parent = canvas.parentElement;
if (!parent) return;
const w = parent.clientWidth;
const h = parent.clientHeight;
canvas.width = w;
canvas.height = h;
sizeState = { w, h };
}
function init() {
resize();
const { w, h } = sizeState;
particles = [];
for (let i = 0; i < props.count; i++) {
particles.push(createParticle(w, h));
}
}
function update() {
const { w, h } = sizeState;
const mouseForce = 0.08;
const maxV = props.speed * 3;
for (const p of particles) {
if (mouse.active) {
const dx = p.x - mouse.x;
const dy = p.y - mouse.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < props.mouseRadius && dist > 0) {
const force = (props.mouseRadius - dist) / props.mouseRadius;
p.vx += (dx / dist) * force * mouseForce;
p.vy += (dy / dist) * force * mouseForce;
}
}
p.vx *= 0.99;
p.vy *= 0.99;
p.vx = Math.max(-maxV, Math.min(maxV, p.vx));
p.vy = Math.max(-maxV, Math.min(maxV, p.vy));
p.x += p.vx;
p.y += p.vy;
if (p.x < 0) {
p.x = 0;
p.vx *= -1;
}
if (p.x > w) {
p.x = w;
p.vx *= -1;
}
if (p.y < 0) {
p.y = 0;
p.vy *= -1;
}
if (p.y > h) {
p.y = h;
p.vy *= -1;
}
}
}
function draw(ctx) {
const { w, h } = sizeState;
const { r, g, b } = props.color;
ctx.clearRect(0, 0, w, h);
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const a = particles[i];
const bp = particles[j];
const dx = a.x - bp.x;
const dy = a.y - bp.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < props.connectionDistance) {
const alpha = (1 - dist / props.connectionDistance) * 0.3;
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(bp.x, bp.y);
ctx.strokeStyle = `rgba(${r},${g},${b},${alpha})`;
ctx.lineWidth = 0.5;
ctx.stroke();
}
}
}
for (const p of particles) {
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fillStyle = `rgba(${r},${g},${b},${p.opacity})`;
ctx.fill();
ctx.beginPath();
ctx.arc(p.x, p.y, p.size * 2, 0, Math.PI * 2);
ctx.fillStyle = `rgba(${r},${g},${b},${p.opacity * 0.15})`;
ctx.fill();
}
if (mouse.active) {
const gradient = ctx.createRadialGradient(
mouse.x,
mouse.y,
0,
mouse.x,
mouse.y,
props.mouseRadius
);
gradient.addColorStop(0, `rgba(${r},${g},${b},0.08)`);
gradient.addColorStop(1, "transparent");
ctx.beginPath();
ctx.arc(mouse.x, mouse.y, props.mouseRadius, 0, Math.PI * 2);
ctx.fillStyle = gradient;
ctx.fill();
}
}
function handleMouseMove(e) {
const canvas = canvasRef.value;
if (!canvas) return;
const rect = canvas.getBoundingClientRect();
mouse.x = e.clientX - rect.left;
mouse.y = e.clientY - rect.top;
mouse.active = true;
}
function handleMouseLeave() {
mouse.active = false;
}
function handleResize() {
resize();
}
onMounted(() => {
const canvas = canvasRef.value;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
canvas.addEventListener("mousemove", handleMouseMove);
canvas.addEventListener("mouseleave", handleMouseLeave);
window.addEventListener("resize", handleResize);
init();
function loop() {
update();
draw(ctx);
animId = requestAnimationFrame(loop);
}
loop();
});
onUnmounted(() => {
cancelAnimationFrame(animId);
const canvas = canvasRef.value;
if (canvas) {
canvas.removeEventListener("mousemove", handleMouseMove);
canvas.removeEventListener("mouseleave", handleMouseLeave);
}
window.removeEventListener("resize", handleResize);
});
</script>
<template>
<div style="position: relative; width: 100%; height: 100%">
<canvas ref="canvasRef" style="display: block; width: 100%; height: 100%" />
<div
style="
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
pointer-events: none;
z-index: 10;
"
>
<h1
style="
font-size: clamp(2rem, 5vw, 3.5rem);
font-weight: 800;
letter-spacing: -0.03em;
background: linear-gradient(135deg, #e0e7ff 0%, #818cf8 50%, #6366f1 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 0.5rem;
"
>
Particles System
</h1>
<p style="font-size: 1rem; color: rgba(148,163,184,0.7)">
Move your mouse to interact with the particles
</p>
</div>
</div>
</template>
<style scoped>
</style><script>
import { onMount, onDestroy } from "svelte";
export let count = 120;
export let color = { r: 99, g: 102, b: 241 };
export let connectionDistance = 150;
export let speed = 0.4;
export let mouseRadius = 180;
export let className = "";
let canvasEl;
let particles = [];
let mouse = { x: -9999, y: -9999, active: false };
let animId = 0;
let sizeState = { w: 0, h: 0 };
function createParticle(w, h) {
return {
x: Math.random() * w,
y: Math.random() * h,
vx: (Math.random() - 0.5) * speed * 2,
vy: (Math.random() - 0.5) * speed * 2,
size: 1 + Math.random() * 2,
opacity: 0.3 + Math.random() * 0.5,
};
}
function resize() {
const parent = canvasEl.parentElement;
if (!parent) return;
const w = parent.clientWidth;
const h = parent.clientHeight;
canvasEl.width = w;
canvasEl.height = h;
sizeState = { w, h };
}
function init() {
resize();
const { w, h } = sizeState;
particles = [];
for (let i = 0; i < count; i++) {
particles.push(createParticle(w, h));
}
}
function update() {
const { w, h } = sizeState;
const mouseForce = 0.08;
const maxV = speed * 3;
for (const p of particles) {
if (mouse.active) {
const dx = p.x - mouse.x;
const dy = p.y - mouse.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < mouseRadius && dist > 0) {
const force = (mouseRadius - dist) / mouseRadius;
p.vx += (dx / dist) * force * mouseForce;
p.vy += (dy / dist) * force * mouseForce;
}
}
p.vx *= 0.99;
p.vy *= 0.99;
p.vx = Math.max(-maxV, Math.min(maxV, p.vx));
p.vy = Math.max(-maxV, Math.min(maxV, p.vy));
p.x += p.vx;
p.y += p.vy;
if (p.x < 0) {
p.x = 0;
p.vx *= -1;
}
if (p.x > w) {
p.x = w;
p.vx *= -1;
}
if (p.y < 0) {
p.y = 0;
p.vy *= -1;
}
if (p.y > h) {
p.y = h;
p.vy *= -1;
}
}
}
function draw(ctx) {
const { w, h } = sizeState;
const { r, g, b } = color;
ctx.clearRect(0, 0, w, h);
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const a = particles[i];
const bp = particles[j];
const dx = a.x - bp.x;
const dy = a.y - bp.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < connectionDistance) {
const alpha = (1 - dist / connectionDistance) * 0.3;
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(bp.x, bp.y);
ctx.strokeStyle = `rgba(${r},${g},${b},${alpha})`;
ctx.lineWidth = 0.5;
ctx.stroke();
}
}
}
for (const p of particles) {
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fillStyle = `rgba(${r},${g},${b},${p.opacity})`;
ctx.fill();
ctx.beginPath();
ctx.arc(p.x, p.y, p.size * 2, 0, Math.PI * 2);
ctx.fillStyle = `rgba(${r},${g},${b},${p.opacity * 0.15})`;
ctx.fill();
}
if (mouse.active) {
const gradient = ctx.createRadialGradient(mouse.x, mouse.y, 0, mouse.x, mouse.y, mouseRadius);
gradient.addColorStop(0, `rgba(${r},${g},${b},0.08)`);
gradient.addColorStop(1, "transparent");
ctx.beginPath();
ctx.arc(mouse.x, mouse.y, mouseRadius, 0, Math.PI * 2);
ctx.fillStyle = gradient;
ctx.fill();
}
}
function handleMouseMove(e) {
const rect = canvasEl.getBoundingClientRect();
mouse.x = e.clientX - rect.left;
mouse.y = e.clientY - rect.top;
mouse.active = true;
}
function handleMouseLeave() {
mouse.active = false;
}
function handleResize() {
resize();
}
onMount(() => {
const ctx = canvasEl.getContext("2d");
if (!ctx) return;
canvasEl.addEventListener("mousemove", handleMouseMove);
canvasEl.addEventListener("mouseleave", handleMouseLeave);
window.addEventListener("resize", handleResize);
init();
function loop() {
update();
draw(ctx);
animId = requestAnimationFrame(loop);
}
loop();
});
onDestroy(() => {
cancelAnimationFrame(animId);
if (typeof window !== "undefined") {
window.removeEventListener("resize", handleResize);
}
});
</script>
<div class={className} style="position: relative; width: 100%; height: 100%;">
<canvas bind:this={canvasEl} style="display: block; width: 100%; height: 100%;" />
<div
style="
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
pointer-events: none;
z-index: 10;
"
>
<h1
style="
font-size: clamp(2rem, 5vw, 3.5rem);
font-weight: 800;
letter-spacing: -0.03em;
background: linear-gradient(135deg, #e0e7ff 0%, #818cf8 50%, #6366f1 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 0.5rem;
"
>
Particles System
</h1>
<p style="font-size: 1rem; color: rgba(148,163,184,0.7);">
Move your mouse to interact with the particles
</p>
</div>
</div>Particles System
An interactive canvas particle system where floating particles are connected by delicate lines when they drift close together. The mouse cursor acts as a force field, attracting or repelling particles on hover.
How it works
- A full-screen HTML Canvas renders particles with random positions and velocities
- Each frame, particles update positions, bounce off edges, and draw connecting lines to nearby neighbors
- Mouse movement creates an attraction/repulsion force that influences particle velocity
requestAnimationFramedrives the render loop at native refresh rate
Customization
count— number of particlescolor— particle and connection line colorconnectionDistance— maximum distance for lines between particlesparticleSize— radius range for particlesspeed— velocity multiplier- Mouse interaction mode: attraction vs repulsion
When to use it
- Hero section animated backgrounds
- Landing page ambient effects
- Network or connection visualization metaphors
- Interactive decorative overlays