<canvas>
body {
  margin: 0;
  overflow: hidden;
}

canvas {
  display: block;
}
const STG = {
  nbParticles: 50
}

let W, H
const cvs = document.querySelector("canvas")
const ctx = cvs.getContext("2d")
cvs.width = W = window.innerWidth
cvs.height = H = window.innerHeight

// create an offscreen canvas which will be used to draw the subtrate from the particles
const offCvs = document.createElement("canvas")
const offCtx = offCvs.getContext("2d")
offCvs.width = W
offCvs.height = H

// initialize the particles
const particles = []
for (let i = 0; i < STG.nbParticles; i++) {
  particles[i] = {
    x: Math.random() * W,
    y: Math.random() * H,
    vx: (Math.random() - 0.5) * .5,
    vy: (Math.random() - 0.5) * .5
  }
}


// toroidal topology (coordinates that loop on the edges)
const toroidal = (x, y) => {
  if (x > W) x%= W
  else if (x < 0) x = W + x
  if (y > H) y%= H
  else if (y < 0) y = H + y
  return { x, y }
}


const update = () => {
  // move the particles
  for (const p of particles) {
    const { x, y } = toroidal(p.x + p.vx, p.y + p.vy)
    p.x = x
    p.y = y
  }
  
  // apply some decay to the offscreen canvas
  offCtx.globalCompositeOperation = "normal"
  offCtx.globalAlpha = 0.05
  offCtx.fillStyle = `rgba(0, 0, 0, 1)`
  offCtx.fillRect(0, 0, W, H)
  
  // draw to the substrate map (ie the offscreen canvas)
  offCtx.globalAlpha = 0.2
  offCtx.globalCompositeOperation = "lighter"
  for (const p of particles) {
    const grad = offCtx.createRadialGradient(p.x, p.y, 3, p.x, p.y, 50)
    grad.addColorStop(0, "#555555")
    grad.addColorStop(1, "transparent")
    offCtx.beginPath()
    offCtx.arc(p.x, p.y, 50, 0, 2 * Math.PI)
    offCtx.fillStyle = grad
    offCtx.fill()
  }
}


const draw = () => {
  // draw the substrate
  ctx.drawImage(offCvs, 0, 0)
  
  // draw the particles
  ctx.fillStyle = "red"
  for (const p of particles) {
    ctx.beginPath()
    ctx.arc(p.x, p.y, 4, 0, 2*Math.PI)
    ctx.fill()
  }
}


const loop = () => {
  requestAnimationFrame(loop)
  update()
  draw()
}

loop()

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.