Skip to content

Particles ​

Noise Swirl: 600 particles drifting along a noise field without clearing the canvas, so they leave trails. Made for Genuary 2024, "Particles, lots of them", after an OpenProcessing sketch.

js
const replay = Inputs.button("Replay");
js
replay;

const width = 640;
const height = Math.round(width * 0.618);
const speed = 0.4;
const scale = 800;
const count = 200;
const noise = cm.noise(0, Math.PI * 2 * scale);
const random = (a, b) => a + Math.random() * (b - a);
const particles = Array.from({length: count * 3}, (_, i) => ({
  type: Math.floor(i / count),
  index: i % count,
  location: cm.vec(random(0, width), random(0, height)),
}));
const renderer = cm.canvas({width, height});
const root = cm.flow().renderer(renderer);

function move(d) {
  const angle = noise(d.location.x / scale, d.location.y / scale);
  d.location.add(cm.vecFromAngle(angle).mult(speed));
  const {x, y} = d.location;
  if (x < 0 || x > width || y < 0 || y > height) d.location.set(random(50, width), random(50, height));
}

root.append(cm.clear, {fill: "rgb(21, 8, 50)"});

requestAnimationFrame(function frame() {
  root
    .data(particles)
    .process(cm.each, move)
    .append(cm.circle, {
      x: (d) => d.location.x,
      y: (d) => d.location.y,
      r: (d) => d.index,
      fill: (d) => d.type,
      fillOpacity: (d) => d.index,
    })
    .transform(cm.mapAttrs, {
      r: {range: [0.5, 1]},
      fillOpacity: {range: [0, 1]},
      fill: {scale: cm.scaleOrdinal, range: ["rgb(69, 33, 124)", "rgb(7, 153, 242)", "white"]},
    });

  root.render();
  requestAnimationFrame(frame);
});

document.body.append(renderer.node());