Back to Blog

Particle Network Background with Canvas (No Library)

Celest KimCelest Kim

Video: Particle Network Background with Canvas (No Library) by CelesteAI

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

The animated “connecting dots” background — points drifting around a hero section, lines snapping between the ones that get close — is one of the most-requested front-end effects. People reach for libraries like particles.js to get it. You don’t need one. It’s about sixty lines of plain Canvas 2D.

This build uses TypeScript and Vite, scaffolded from the command line and run in the browser. The only dependency is Vite itself — there’s no graphics library at all.

Set up the project

npm create vite@latest particle-network -- --template vanilla-ts
cd particle-network
npm install

That’s it — no npm install of any graphics library, because Canvas is built into the browser. Trim index.html to a full-screen canvas:

<canvas id="bg"></canvas>
<script type="module" src="/src/main.ts"></script>
html, body { margin: 0; height: 100%; background: #070b18; }
#bg { position: fixed; inset: 0; display: block; }

The canvas and the particles

Grab the canvas and its 2D context, and decide on a few constants. Each particle is just a position and a velocity.

const canvas = document.getElementById("bg") as HTMLCanvasElement;
const ctx = canvas.getContext("2d") as CanvasRenderingContext2D;

const COUNT = 110;
const MAX_DIST = 150;   // connect points closer than this
const SPEED = 0.5;

interface Particle { x: number; y: number; vx: number; vy: number; }
const particles: Particle[] = [];

function resize() {
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
}

function init() {
  particles.length = 0;
  for (let i = 0; i < COUNT; i++) {
    particles.push({
      x: Math.random() * canvas.width,
      y: Math.random() * canvas.height,
      vx: (Math.random() - 0.5) * SPEED * 2,
      vy: (Math.random() - 0.5) * SPEED * 2,
    });
  }
}

The frame loop

Every frame: clear the canvas, move each particle (bouncing off the edges), then the interesting part — check every pair of particles and draw a line between any two that are close, fading the line with distance. That fade is what turns a mess of lines into a living network. Finally, draw the dots on top and ask for the next frame.

function frame() {
  ctx.fillStyle = "#070b18";
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  for (const p of particles) {
    p.x += p.vx;
    p.y += p.vy;
    if (p.x < 0 || p.x > canvas.width) p.vx *= -1;
    if (p.y < 0 || p.y > canvas.height) p.vy *= -1;
  }

  for (let i = 0; i < COUNT; i++) {
    for (let j = i + 1; j < COUNT; j++) {
      const dist = Math.hypot(particles[i].x - particles[j].x, particles[i].y - particles[j].y);
      if (dist < MAX_DIST) {
        const alpha = (1 - dist / MAX_DIST) * 0.55;
        ctx.strokeStyle = "rgba(94, 234, 212, " + alpha + ")";
        ctx.beginPath();
        ctx.moveTo(particles[i].x, particles[i].y);
        ctx.lineTo(particles[j].x, particles[j].y);
        ctx.stroke();
      }
    }
  }

  ctx.fillStyle = "#a5f3ec";
  for (const p of particles) {
    ctx.beginPath();
    ctx.arc(p.x, p.y, 2, 0, Math.PI * 2);
    ctx.fill();
  }

  requestAnimationFrame(frame);
}

window.addEventListener("resize", () => { resize(); init(); });
resize();
init();
frame();

The pair check is O(n²) — fine for ~100–150 particles. If you push the count into the thousands, switch to a spatial grid so you only compare nearby points.

Run it

npm run dev

Open the URL Vite prints and the network drifts and connects on its own. Drop the canvas behind your content (a low z-index, position: fixed) and it’s a background.

Recap

  • Canvas 2D needs no library. getContext("2d"), then you draw every frame yourself.
  • The network is just a pairwise distance check — connect points closer than a threshold.
  • Fade the line with distance. Bright when close, faint when far — that’s what makes it read as a living web instead of a tangle of lines.

Want the deeper dive?

The full TypeScript project is on GitHub — clone it, npm install, npm run dev, and tune the count, distance, and colors.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.