<canvas id="canvas"> Fallback text </canvas>
#canvas {
  background: black;
}
body {
  margin: 0px;
}
/* Next time, update the colors */
/* pass in lifetime to the drawCircle method to make it fade over time */

const COLORS = ["red", "orange", "turquoise", "blue", "purple", "pink", "yellow"];

var utils = (function() {
  return {
    randomPositiveOrNegative: function () {
      return (Math.round(Math.random()) * 2 - 1);
    },
    getRandomArbitrary: function (min, max) {
      return Math.floor(Math.random() * (max - min) + min);
    }
  };
})();

const canvas = document.getElementById("canvas");
const context = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

let formations = [];

class Particle {
  constructor(x, y, angle, speed, color) {
    this.x = x;
    this.y = y;
    this.angle = angle;
    this.speed = speed;
    this.color = color;
    this.radius = 10;
   }
  
  drawCircle = () => {
    context.beginPath();
    context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
    
    // this.gradient = context.createRadialGradient(this.x, this.y, 1, this.x, this.y, this.radius);
    // this.gradient.addColorStop(0.15, this.color);
    // this.gradient.addColorStop(0.95, oklch(60% 0.15 50));
    context.fillStyle = this.color;
    context.fill();
   }
  
  moveCircle = () => {
    this.x = this.x + (this.speed * Math.cos(this.angle));
    this.y = this.y + (this.speed * Math.sin(this.angle));
    this.angle += .03;
    // this.angle = this.angle > (2 * Math.PI) ? 0 : this.angle;
    /*this.x = this.x + (this.speed * Math.cos(this.angle));
    this.y = this.y + (this.speed * Math.sin(this.angle));
    this.angle += .01;
    this.angle = this.angle > (2 * Math.PI) ? 0 : this.angle;*/
  }
}

class Formation {
  constructor(x, y, particleCount, speed) {
    this.particleCount = particleCount;
    this.speed = utils.getRandomArbitrary(1,4);
    this.particles = [];
    this.lifetime = 0;
    this.drawIntervals = 1;
    this.currDrawInterval = 0;
    this.shouldDraw = true;
    this.color = COLORS[utils.getRandomArbitrary(0, COLORS.length - 1)];
    for (let i = 0; i < this.particleCount ; i++ ) {
      const radianAngle = 2/ this.particleCount * Math.PI * (i + 1);
      this.particles.push(new Particle(x, y, radianAngle, this.speed, this.color));
    }
  }
  
  moveFormation() {
    for (let i = 0; i < this.particleCount ; i++ ) {
      const particle = this.particles[i];
      
      if (this.shouldDraw) {
        particle.drawCircle();
      }
      
      particle.moveCircle();
    }
  this.lifetime++;
  
   if (this.currDrawInterval > this.drawIntervals ) {
     this.currDrawInterval = 0;
     this.shouldDraw = !this.shouldDraw;
   } else {
     this.currDrawInterval++;
   }
  }
  
  getLifetime() {
    return this.lifetime;
  }
}

function offsetSpirals(spiralCount, x, y) {
  setTimeout(function() {
    // main logic
    console.log(`count ${spiralCount}`); 
    createSpiral(randomX, randomY)
    
    //  call again if spiralCount > 0
    if (--spiralCount) offsetSpirals(spiralCount); 
  }, 400)
}

async function createMultipleSpirals() {
  const numberOfSpirals = utils.getRandomArbitrary(3,7);
  // console.log(numberOfSpirals)
  
  randomX = utils.getRandomArbitrary(canvas.width/5,canvas.width*4/5);
    randomY = utils.getRandomArbitrary(canvas.height/5,canvas.height*4/5);
  
  // for (let i = 0; i < numberOfSpirals ; i++ ) {
  //   createSpiral(randomX, randomY)
  // }
  offsetSpirals(numberOfSpirals, randomX, randomY);
}

const createSpiral = (x, y) => {
  const numberOfParticles = utils.getRandomArbitrary(5,20);
  formations.push(new Formation(x, y, numberOfParticles));
}

const handleClick = event => {
  const x = event.pageX;
  const y = event.pageY;
  
  createSpiral(x, y);
}

const animate = () => {
  // clear context
  //context.clearRect(0, 0, canvas.width, canvas.height);
  // instead of completely clearing, only partially clear so it shows a little of past frames
  context.globalAlpha = 0.1;
  context.fillStyle = "black";
  context.fillRect(0, 0, canvas.width, canvas.height);
  context.globalAlpha = 1.0;

  
  for (let i = 0; i < formations.length; i++) {
    // draw particles and move them along
      const formation = formations[i];
      formation.moveFormation();
      
      const MAX_LIFETIME = 500;
      if (formation.getLifetime() > MAX_LIFETIME) {
      // then remove the formation (using i, its index)
        formations = formations.slice(0, i).concat(formations.slice(i+1));
      }
  }
  
  requestAnimationFrame(animate);
}

animate();
setInterval(createMultipleSpirals, 5000);
canvas.addEventListener('click', handleClick);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.