Javascript on Click Confetti Effect
📣 Sponsor
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.
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 div
s. We will be assigning them all random colors.
To do that, the code looks like this:
.confetti-item {
width: 10px;
position: absolute;
top: 0;
left: 0;
height: 10px;
}
.confetti {
position: fixed;
top: 0;
left: 0;
}
// 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
createElement.style.position = `fixed`;
createElement.style.top = `${y}px`;
createElement.style.left = `${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.
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:
// 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.
// 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;
}
item.style.opacity = 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.
item.style.transform = `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);
Finally
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
More Tips and Tricks for Javascript
- How does the Javascript logical AND (&&) operator work?
- Creating and Generating UUIDs with Javascript
- How fetch works in Javascript
- The Javascript API to Access a User's Local Files
- Converting Binary to Decimal
- Javascript Errors
- Javascript Variables
- How does Optional Chaining work in Javascript?
- How the Javascript History API Works
- A Look at the New Array Methods coming to Javascript