AJ Jones
AJ Jones

Reputation: 55

Infinite Loop Confetti Loop

I have created a fancy confetti animation but it seems to run infinitely. I only want it to run for a few seconds and stop gracefully. This is my first time working with requestAnimationFrame so I would not know where to stop the animation. I have tried to use a simple counter variable around the update but I have noticed that I have to multiply potential endless loops that will still run in the background eating resources. This snippet of code does not seem to fully stop all the functions from running in a never-ending loop.

let timer = 0;
const drawConfetti = () => {
  ctx.clearRect(0, 0, wW, wH);
  timer++;
  
  confetties.forEach(confetti => {
    if (timer < 1500){
      confetti.update();
    }else{
      return
    }
  });
  
  requestAnimationFrame(drawConfetti);

I have updated the code below to the first answer suggested but now the animation is stops mid rendering now.

const AMOUNT = 150;
const INTERVAL = 300;
const COLORS = ['#4579FF', '#29EAFC', '#FAB1C0', '#50E3C2', '#FFFC9D', '#1A04B3', '#F81C4D'];
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const wW = window.innerWidth;
const wH = window.innerHeight;

const random = (min, max) => {
  return Math.random() * (max - min) + min;
};

const randomInt = (min, max) => {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min)) + min;
};

const confetties = [];

class Confetti {
  constructor(width, height, color, speed, x, y, rotation) {
    this.width = width;
    this.height = height;
    this.color = color;
    this.speed = speed;
    this.x = x;
    this.y = y;
    this.rotation = rotation;
  }

  update() {
    const y = this.y < wH ? this.y += this.speed : -20;
    const x = this.x + Math.sin(this.y * (this.speed / 100));
    this.x = x > wW ? 0 : x;
    this.y = y;
    ctx.fillStyle = this.color;
    ctx.save();
    ctx.translate(x + (this.width / 2), y + (this.height / 2));
    ctx.rotate((y * this.speed) * Math.PI / 180);
    ctx.scale(Math.cos(y / 10), 1);
    ctx.fillRect(-this.width / 2, -this.height / 2,
      this.width,
      this.height
    );
    ctx.restore();
  }
}

canvas.width = wW;
canvas.height = wH;
let animationId;

const drawConfetti = () => {
  ctx.clearRect(0, 0, wW, wH);

  confetties.forEach(confetti => {
    confetti.update();
  });

  animationId = requestAnimationFrame(drawConfetti);
}

const renderConfetti = () => {
  let count = 0;
  let stagger = setInterval(() => {
    if (count < AMOUNT) {
      const x = random(0, wW);
      const speed = random(1.0, 2.0);
      const width = 24 / speed;
      const height = 48 / speed;
      const color = COLORS[randomInt(0, COLORS.length)];
      const confetti = new Confetti(width, height, color, speed, x, -20, 0);
      confetties.push(confetti);
    } else {
      clearInterval(stagger);
    }
    count++;
  }, INTERVAL);

  drawConfetti();
}

renderConfetti();

function stop() {
  cancelAnimationFrame(animationId)
}

setTimeout(stop, 5000);
body {
  margin: 0;
  background-color: #000;
}
<canvas id="canvas"></canvas>

Upvotes: 0

Views: 868

Answers (1)

Gianluca Fuoco
Gianluca Fuoco

Reputation: 303

The requestAnimationFrame funciton is being called recursively. If you want to stop it, you should use cancelAnimationFrame. To do this, save the ID of the animation outside of the drawLoop function.

e.g

let animationId;

const drawConfetti = () => {
  ctx.clearRect(0, 0, wW, wH);

  confetties.forEach(confetti => {
    confetti.update();
  });

  animationId = requestAnimationFrame(drawConfetti);
}

// now cancel whenever you want
cancelAnimationFrame(animationId)

Upvotes: 1

Related Questions