Javascript on Click Confetti Effect

I am currently working on a project at the minute which rewards users if they click on a correct answer. Giving users a response which matches the way they should feel when they give a correct answer, is an important part of the user experience.

I thought about a few ways to do this - maybe when the user clicks on a correct response, the button shakes in a positive way. I didn't have much luck with this, so my mind turned to confetti, which is (for all intents and purposes) solely associated with celebration.

It is worth mentioning at this point that this page is confetti enabled. Click anywhere, and confetti should appear from the end of your cursor, or whever you touch.

Link to Demo Image of confetti

Fun fact: while doing this, I also discovered that confetti was used as far back as 14th century Milan, which was far longer than I expected.

How it works

Let's think about the fundamentals of how this effect might work:

  • First: we need to create all of the individual pieces of confetti.
  • Next: we need to move them as confetti would move, i.e. reach a peak, and then fall. 🎉
  • Finally: we need to fade out and remove the confetti.

To create the confetti, we will be making lots of tiny divs. We will be assigning them all random colors.

To do that, the code looks like this:

css Copy
.confetti-item { width: 10px; position: absolute; top: 0; left: 0; height: 10px; } .confetti { position: fixed; top: 0; left: 0; }
javascript Copy
// Create a confetti holder element let createElement = document.createElement('div'); createElement.classList.add('confetti'); // These are our 'random' colors let colors = [ '#2162ff', '#9e21ff', '#21a9ff', '#a9ff21', '#ff2184' ] // Let's generate some confetti. The function below is generating 40 random confetti items! let confettiHTML = ''; for(var i = 0; i < 20; ++i) { let color = Math.floor(Math.random() * (colors.length)); confettiHTML += `<div class="confetti-item" style="background-color: ${colors[color]};" data-angle="${Math.random()}" data-speed="${Math.random()}"></div>`; confettiHTML += `<div class="confetti-item reverse" style="background-color: ${colors[color]};" data-angle="${Math.random()}" data-speed="${Math.random()}"></div>`; } // Updates the style of our new confetti element and appends it to the body = `fixed`; = `${y}px`; = `${x}px`; createElement.innerHTML = confettiHTML; document.body.appendChild(createElement);

Now we've created the structure of our confetti, we have to move onto it's movement.


Modelling movement requires equations. For this piece, we are going to be using the traditional equations of projectile motion. If you are unfamiliar with these, they model the movement of an object along the x and y axis from a starting point, given a certain speed.

The equations look like this:

javascript Copy
// x = speed * time * cos(angle) // y = speed * time * sin(angle) - (0.5 * gravity * time^2)

We will use these core equations to create our motion, and add some modifiers to it to create the sense of 'random' motion. As an example, here is one object moving with projectile motion:

The effect above is generated using these projectile motion equations, and simply translating the element according to their values.

Implementation of Projectile Motion

As we are applying this to every confetti item, we need to go through each item and adjust its motion accordingly. You may have noticed earlier that we have two data tags on each confetti element, both of which have random numbers. We will use these random numbers to generate a sense of random motion.

We will have a random number for the angle and speed, and then adjust the position of each confetti element by its random number.

We will be using an interval with a time interval of 33.3 microseconds.

javascript Copy
// The gravity or 'falling speed' adjuster let gravity = 50; // Fjolt is a high gravity planet // The range of speeds let maxSpeed = 105000; // Pixels * 1000 let minSpeed = 65000; // Pixels * 1000 // The time let t = 0; // Time starts at 0 // The range of angles let maxAngle = 1500; // Radians * 1000 let minAngle = 400; // Radians * 1000 // The initial opacity let opacity = 1; // The initial confetti rotation let rotateAngle = 0; // The interval let interval = setInterval(function() { // Generate motion for each confetti element document.querySelectorAll(`.confetti-item`).forEach(function(item) { // So we can have two directions of confetti, we use a modifier on the X axis. let modifierX = 1; if(item.classList.contains('reverse')) { modifierX = -1; } = opacity; // Get both our random numbers let randomNumber = parseFloat(item.getAttribute('data-angle')); let otherRandom = parseFloat(item.getAttribute('data-speed')); // Generate a random angle motion for rotating each confetti element let newRotateAngle = randomNumber * rotateAngle; // Generate the angle and speed we use for the equations of motion let angle = (randomNumber * (maxAngle - minAngle) + minAngle) / 1000; let speed = (randomNumber * (maxSpeed - minSpeed) + minSpeed) / 1000; // Get x,y for projectile motion. We adjust this by the random number. let x = speed * t * Math.cos(angle) + (50 * otherRandom * t); let y = speed * t * Math.sin(angle) - (0.5 * gravity * Math.pow(t, 2)) + (50 * otherRandom * t); // Transform the element accordingly. = `translateX(${x * modifierX}px) translateY(${y * -1}px) rotateY(${newRotateAngle}deg) scale(${1})`; }) // Increase time, and rotate angle // Decrease opacity t += 0.1; rotateAngle += 3; opacity -= 0.02; // If the time is above 6, then lets remove the element if(t >= 6) { t = 0.1; if(document.querySelector(`.confetti`) !== null) { console.log(document.querySelector(`.confetti`)); document.querySelector(`.confetti`).remove(); } clearInterval(interval); } }, 33.33);


In the final code, I've given each confetti instance an individual ID, so we can have multiple instances of confetti on the page. There are a few other additions too, which make it a little bit more interactive.

This final code can be found in the codepen demo.

If you've enjoyed this article, please consider sharing it, as it really helps us out. If you have any questions, you can reach us on Twitter

Last Updated Saturday, 10 April 2021