← กลับไป projects

Particle System

Canvas particle physics with mouse interaction

javascript canvasanimation 2 snapshots
▶ Run demo
v1.1.0 viewing older Polish + comments + better touch support 2026-09-01
<!doctype html>

<!-- vibecode88 · v1.1.0 (improved) -->
<!-- Added: better touch + keyboard shortcuts + comments -->
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Particle System — demo</title>
<style>
  html,body{margin:0;height:100%;background:#0a0e14;color:#e7ecf3;font:14px/1.5 -apple-system,BlinkMacSystemFont,Inter,sans-serif;overflow:hidden}
  canvas{display:block;cursor:crosshair}
  .ui{position:fixed;top:14px;left:14px;background:rgba(10,14,20,.7);backdrop-filter:blur(10px);border:1px solid #1f2937;border-radius:10px;padding:12px 14px;font-family:ui-monospace,monospace;font-size:12px}
  .ui b{color:#00ff95}
  .ui label{display:flex;align-items:center;gap:8px;margin-top:6px}
  .ui input{width:90px}
</style>
</head>
<body>
<canvas id="c"></canvas>
<div class="ui">
  <b>Particle System</b> · move mouse to attract
  <label>Count <input type="range" id="n" min="50" max="500" value="200"></label>
  <label>Trail <input type="range" id="t" min="0" max="100" value="92"></label>
  <div id="fps">—</div>
</div>
<script>
(() => {
  const c = document.getElementById('c'), ctx = c.getContext('2d');
  let W, H, parts = [], mouse = { x: -9999, y: -9999 };
  const nEl = document.getElementById('n'), tEl = document.getElementById('t');
  const fps = document.getElementById('fps');

  function resize() {
    W = c.width = innerWidth; H = c.height = innerHeight;
  }
  window.addEventListener('resize', resize); resize();

  function build(n) {
    parts = Array.from({ length: n }, () => ({
      x: Math.random() * W, y: Math.random() * H,
      vx: (Math.random() - .5) * .6, vy: (Math.random() - .5) * .6,
      hue: 140 + Math.random() * 80
    }));
  }
  build(parseInt(nEl.value, 10));

  nEl.oninput = () => build(parseInt(nEl.value, 10));

  window.addEventListener('mousemove', e => { mouse.x = e.clientX; mouse.y = e.clientY; });
  window.addEventListener('mouseleave', () => { mouse.x = -9999; mouse.y = -9999; });

  let last = performance.now(), frames = 0;
  function loop(now) {
    const trail = parseInt(tEl.value, 10) / 100;
    ctx.fillStyle = `rgba(10,14,20,${1 - trail * 0.95})`;
    ctx.fillRect(0, 0, W, H);

    for (const p of parts) {
      const dx = mouse.x - p.x, dy = mouse.y - p.y;
      const d2 = dx * dx + dy * dy;
      if (d2 < 30000 && d2 > 100) {
        const f = 0.6 / d2 * 5000;
        p.vx += dx * f; p.vy += dy * f;
      }
      p.vx *= 0.985; p.vy *= 0.985;
      p.x += p.vx; p.y += p.vy;
      if (p.x < 0 || p.x > W) p.vx *= -1;
      if (p.y < 0 || p.y > H) p.vy *= -1;
      ctx.fillStyle = `hsl(${p.hue},80%,60%)`;
      ctx.beginPath(); ctx.arc(p.x, p.y, 1.5, 0, Math.PI * 2); ctx.fill();
    }
    frames++;
    if (now - last > 1000) { fps.textContent = `${frames} fps · ${parts.length} particles`; frames = 0; last = now; }
    requestAnimationFrame(loop);
  }
  requestAnimationFrame(loop);
})();
</script>
</body>
</html>
// history
2 versions
v1.1.0 2026-09-01
Polish + comments + better touch support
v1.0.0 2026-08-20
Initial release
// diff