Search code examples
javascriptcssanimationcanvasdraw

Animating multiple circles in a canvas


I'm trying to make an animation inside a canvas: here, a numbered circle must be drawn and move from left to right one single time, disappearing as soon as it reaches the end of animation.

For now I managed to animate it in loop, but I need to animate at the same time (or with a set delay) multiple numbered circles, strating in different rows (changing y position) so they wont overlap.

Any idea how can I manage this? my JS code is the following:

// Single Animated Circle - Get Canvas element by Id
var canvas = document.getElementById("canvas");

// Set Canvas dimensions
canvas.width = 300;
canvas.height = 900;

// Get drawing context
var ctx = canvas.getContext("2d");

// Radius
var radius = 13;
// Starting Position
var x = radius;
var y = radius;

// Speed in x and y direction
var dx = 1;
var dy = 0;

// Generate random number
var randomNumber = Math.floor(Math.random() * 60) + 1;

if (randomNumber > 0 && randomNumber <= 10) {
  ctx.strokeStyle = "#0b0bf1";
} else if (randomNumber > 10 && randomNumber <= 20) {
  ctx.strokeStyle = "#f10b0b";
} else if (randomNumber > 20 && randomNumber <= 30) {
  ctx.strokeStyle = "#0bf163";
} else if (randomNumber > 30 && randomNumber <= 40) {
  ctx.strokeStyle = "#f1da0b";
} else if (randomNumber > 40 && randomNumber <= 50) {
  ctx.strokeStyle = "#950bf1";
} else if (randomNumber > 50 && randomNumber <= 60) {
  ctx.strokeStyle = "#0bf1e5";
}

function animate3() {
  requestAnimationFrame(animate3);

  ctx.clearRect(0, 0, 300, 900);

  if (x + radius > 300 || x - radius < 0) {
    x = radius;
  }

  x += dx;

  ctx.beginPath();
  ctx.arc(x, y, 12, 0, Math.PI * 2, false);
  ctx.stroke();
  ctx.fillText(randomNumber, x - 5, y + 3);
}

// Animate the Circle

animate3();
<canvas id="canvas"></canvas>


Solution

  • Here is a solution which doesn't use classes as such and separates the animation logic from the updating - which can be useful if you want more precise control over timing.

    // Some helper functions
    const clamp = (number, min, max) => Math.min(Math.max(number, min), max);
    
    // Choose and remove random member of arr with equal probability 
    const takeRandom = arr => arr.splice(parseInt(Math.random() * arr.length), 1)[0]
    
    // Call a function at an interval, passing the amount of time that has passed since the last call
    function update(callBack, interval) {
      let now = performance.now();
      let last;
      setInterval(function() {
        last = now;
        now = performance.now();
        callBack((now - last) / 1000);
      })
    }
    
    const settings = {
      width: 300,
      height: 150,
      radius: 13,
      gap: 5,
      circles: 5,
      maxSpeed: 100,
      colors: ["#0b0bf1", "#f10b0b", "#0bf163", "#f1da0b", "#950bf1", "#0bf1e5"]
    };
    const canvas = document.getElementById("canvas");
    canvas.width = settings.width;
    canvas.height = settings.height;
    const ctx = canvas.getContext("2d");
    
    // Set circle properties
    const circles = [...Array(settings.circles).keys()].map(i => ({
      number: i + 1,
      x: settings.radius,
      y: settings.radius + (settings.radius * 2 + settings.gap) * i,
      radius: settings.radius,
      dx: settings.maxSpeed * Math.random(), // This is the speed in pixels per second
      dy: 0,
      color: takeRandom(settings.colors)
    }));
    
    function drawCircle(circle) {
      ctx.strokeStyle = circle.color;
      ctx.beginPath();
      ctx.arc(circle.x, circle.y, circle.radius, 0, Math.PI * 2, false);
      ctx.stroke();
      ctx.fillText(circle.number, circle.x - 5, circle.y + 3);
    }
    
    function updateCircle(circle, dt) {
      // Update a circle's position after dt seconds
      circle.x = clamp(circle.x + circle.dx * dt, circle.radius + 1, settings.width - circle.radius - 1);
      circle.y = clamp(circle.y + circle.dy * dt, circle.radius + 1, settings.height - circle.radius - 1);
    }
    
    function animate() {
      ctx.clearRect(0, 0, settings.width, settings.height);
      circles.forEach(drawCircle);
      requestAnimationFrame(animate);
    }
    
    update(dt => circles.forEach(circle => updateCircle(circle, dt)), 50);
    animate();
    <canvas id="canvas" style="border: solid 1px black"></canvas>