Recently, I found myself needing to rebuild a confetti effect, the old one used css and had broken at some point during a stack upgrade. I decided it would be most efficient to use canvas over DOM elements so I fired up my trusty canvas2D template and got to work. I wanted implementation to be rapid so I decided to stick with square and rectangular confetti pieces. First we declare some variables, grabbing the canvas element and the 2D context. The confetti variable is an array of confetti objects which we will be mutating, the rest are constants to control the confetti count, global physics, and colors.

	canvas = document.getElementById("canvas");
	ctx = canvas.getContext("2d");
	canvas.width = window.innerWidth;
	canvas.height = window.innerHeight;
	cx = ctx.canvas.width/2;
	cy = ctx.canvas.height/2;

	let confetti = [];
	const confettiCount = 300;
	const gravity = 0.5;
	const terminalVelocity = 5;
	const drag = 0.075;
	const colors = [
	  { front : 'red', back: 'darkred'},
	  { front : 'green', back: 'darkgreen'},
	  { front : 'blue', back: 'darkblue'},
	  { front : 'yellow', back: 'darkyellow'},
	  { front : 'orange', back: 'darkorange'},
	  { front : 'pink', back: 'darkpink'},
	  { front : 'purple', back: 'darkpurple'},
	  { front : 'turquoise', back: 'darkturquoise'},
	];

Next we populate the confetti array with objects. We choose a random color, dimension, and rotation. The rotation is a random number between 0 and 360 degrees but the canvas rotate method uses radians so I choose a random number between 0 and 2 * Math.PI. The position starts 1px above the bottom of the canvas since later we destroy confetti which hits the bottom. The scale is 1 in both axes because we multiply the dimensions by the scale. Last we set a random velocity which sends the confetti shooting upward when we render. You'll notice I use the randomRange method a lot, it's a helper I created to give me a random number from a negative or positive range.

	initConfetti = () => {
	  for (let i = 0; i < confettiCount; i++) {
	    confetti.push({
	      color      : colors[Math.floor(randomRange(0, colors.length))],
	      dimensions : {
	        x: randomRange(10, 20),
	        y: randomRange(10, 30),
	      },
	      position   : {
	        x: randomRange(0, canvas.width),
	        y: canvas.height - 1,
	      },
	      rotation   : randomRange(0, 2 * Math.PI),
	      scale      : {
	        x: 1,
	        y: 1,
	      },
	      velocity   : {
	        x: randomRange(-25, 25),
	        y: randomRange(0, -50),
	      },
	    });
	  }
	}

	randomRange = (min, max) => Math.random() * (max - min) + min

Now we need to implement all the fun stuff in the render method. I want to make the confetti explode upward as if shot by a cannon, then float downward slowly. We start by clearing the canvas, then open a foreach to render each confetto. The canvas context has a translate and rotate method but the both work relative to the top left corner. In order to render each rectangles rotation we move the entire canvas to the confetto's position, then rotate the canvas.

Next we process the velocity of the confetto, horizontal momentum is dampened over time and randomized to simulate the breeze pushing the confetti around. Vertical momentum is increased by adding gravity until terminal velocity is reached. Now we set the new position of the piece by adding velocity to the position. If a piece is lower than the bottom of the screen, it is removed from the array to improve performance. If a confetto floats out of the screen horizontally it is looped to the other side.

At this point the confetti would float but it would never look like it was spinning in the wind. I simulate this by modifying the confetto's scale in the Y axis using Math.cos() on the confetto's Y position. The end result is that the scale alternates between -1 and 1 as its Y position changes, making the spin relative to the piece's vertical movement speed. To add depth we darken the confetto's color when it's scale is negative.

Last, we draw the rectangle with fillRect() then reset the context's transform matrix which resets the translation and rotation. The only thing left is to fire off another round of confetti when the count gets below 10 and call the animation frame again.

	render = () => {
	  ctx.clearRect(0, 0, canvas.width, canvas.height);

	  confetti.forEach((confetto, index) => {
	    let width = (confetto.dimensions.x * confetto.scale.x);
	    let height = (confetto.dimensions.y * confetto.scale.y);

	    // Move canvas to position and rotate

	    ctx.translate(confetto.position.x, confetto.position.y);
	    ctx.rotate(confetto.rotation);

	    // Apply forces to velocity

	    confetto.velocity.x -= confetto.velocity.x * drag;
	    confetto.velocity.x += Math.random() > 0.5 ? Math.random() : -Math.random();
	    confetto.velocity.y = Math.min(confetto.velocity.y + gravity, terminalVelocity);

	    // Set position

	    confetto.position.x += confetto.velocity.x;
	    confetto.position.y += confetto.velocity.y;

	    // Delete confetti when out of frame

	    if (confetto.position.y >= canvas.height) confetti.splice(index, 1);

	    // Loop confetto x position

	    if (confetto.position.x > canvas.width) confetto.position.x = 0;
	    if (confetto.position.x < 0) confetto.position.x = canvas.width;

	    // Spin confetto by scaling y

	    confetto.scale.y = Math.cos(confetto.position.y * 0.1);
	    ctx.fillStyle = confetto.scale.y > 0 ? confetto.color.front : confetto.color.back;

	    // Draw confetto

	    ctx.fillRect(-width / 2, -height / 2, width, height);

	    // Reset transform matrix

	    ctx.setTransform(1, 0, 0, 1, 0, 0);
	  });

	  // Fire off another round of confetti

	  if (confetti.length <= 10) initConfetti();

	  window.requestAnimationFrame(render);
	}

You can check out the codepen version here: Confetti on codepen.