Confetti Button Using JavaScript
Hello Coder, today we’re going to learn how to Create a Confetti Button. This button can be use as a playful design choice that adds a great user experience to the website.
What is a Confetti Button?
Generally speaking, a virtual button or feature seen on websites, applications, or digital platforms is referred to as a “confetti button”. After activation, an animated celebration occurs, featuring fictitious confetti falling or exploding over the screen.
HTML
We can break down the HTML code into three sections to better understand it.
Let’s Break –
Button Element
This button element has the class “ready” and ID “button”. The JavaScript function “clickButton()” is called when the button is clicked, according to the setting for the “onclick” property.
Message Division within the Button
Submit
Success
Three “” elements and three different states are shown by classes on the button:
“Submit Message”
“Message Loading”
“Success Message”
Every state has a text and SVG icon associated with it.
Canvas Element
The ID “canvas” is attached to an HTML5 “” element. This canvas is probably used to display JavaScript-based confetti animation.
CSS
To create the confetti button and add animations, we can divide the CSS code into ten sections.
Let’s Start –
CSS Variable
:root{
--bgColor: #f4f7ff;
--btnTxtColor: #f4f7ff;
--btnBgPreColor: #1f2335;
--btnSubmitColor: #5c86ff;
--successColor: #5cffa1;
--loadingColor: #5c86ff;
}
These are placeholder values for color that make it simple to change for aesthetically pleasing color schemes.
KeyFrames Animation
@keyframes loading {
0% {
cy: 10;
}
25% {
cy: 3;
}
50% {
cy: 10;
}
}
In this case, we build a keyframe animation called “loading” that controls the SVG circle’s vertical position to simulate the loading effect.
Body and Canvas Style
body {
-webkit-font-smoothing: antialiased;
background-color: var(--bgColor);
}
canvas {
height: 100vh;
pointer-events: none;
position: fixed;
width: 100%;
z-index: 2;
}
Here, we specify the body’s background color and offer confetti animation canvas styles, including position and size.
Button Styling
button {
background: none;
border: none;
color: var(--btnTxtColor);
cursor: pointer;
font-family: "Quicksand", sans-serif;
font-size: 14px;
font-weight: 500;
height: 40px;
left: 50%;
outline: none;
overflow: hidden;
padding: 0 10px;
position: fixed;
top: 50%;
transform: translate(-50%, -50%);
width: 190px;
-webkit-tap-highlight-color: transparent;
z-index: 1;
}
button::before {
background: var(--btnBgPreColor);
border-radius: 50px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4) inset;
content: "";
display: block;
height: 100%;
margin: 0 auto;
position: relative;
transition: width 0.2s cubic-bezier(0.39, 1.86, 0.64, 1) 0.3s;
width: 100%;
}
Here, we specify the styles for the primary button and all of its auxiliary elements, such as transitions, sizes, and states.
Button States Styling
button.ready .submitMessage svg {
opacity: 1;
top: 1px;
transition: top 0.4s ease 600ms, opacity 0.3s linear 600ms;
}
button.ready .submitMessage .button-text span {
top: 0;
opacity: 1;
transition: all 0.2s ease calc(var(--dr) + 600ms);
}
button.loading::before {
transition: width 0.3s ease;
width: 80%;
}
button.loading .loadingMessage {
opacity: 1;
}
button.loading .loadingCircle {
animation-duration: 1s;
animation-iteration-count: infinite;
animation-name: loading;
cy: 10;
}
button.complete .submitMessage svg {
top: -30px;
transition: none;
}
button.complete .submitMessage .button-text span {
top: -8px;
transition: none;
}
button.complete .loadingMessage {
top: 80px;
}
button.complete .successMessage .button-text span {
left: 0;
opacity: 1;
transition: all 0.2s ease calc(var(--d) + 1000ms);
}
button.complete .successMessage svg {
stroke-dashoffset: 0;
transition: stroke-dashoffset 0.3s ease-in-out 1.4s;
}
Here, we offer styles for the “ready,” “loading,” and “success” stages of the button. In every state, specific styles are offered for SVG icons, loading animations, and other visual components.
Text and Messaging Styling
.button-text span {
opacity: 0;
position: relative;
}
.message {
left: 50%;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
width: 100%;
}
.message svg {
display: inline-block;
fill: none;
margin-right: 5px;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2;
}
In this case, we specify the styles for the SVG icons and message containers, as well as the text included within the button.
Submit Message Style
.submitMessage .button-text span {
top: 8px;
transition: all 0.2s ease var(--d);
}
.submitMessage svg {
color: var(--btnSubmitColor);
margin-left: -1px;
opacity: 0;
position: relative;
top: 30px;
transition: top 0.4s ease, opacity 0.3s linear;
width: 14px;
}
Here we define –
- A span element for submitting a message is located within the “Submit Message” section, eight pixels from the top. It transitions from a hidden to visible state using an ease-in-out timing function that is indicated by the CSS character.
- The color of the SVG icon within the “Submit Message” is determined by the variable “btnSubmitColor”. It begins at 30 pixels from the top with 0 opacity and gradually changes in both opacity and top position.
Loading Message Style
.loadingMessage {
opacity: 0;
transition: opacity 0.3s linear 0.3s, top 0.4s cubic-bezier(0.22, 0, 0.41, -0.57);
}
.loadingMessage svg {
fill: var(--loadingColor);
margin: 0;
width: 22px;
}
Here we define –
- It begins with 0 opacity inside the “Loading Message” container and changes linearly over the next 0.3 seconds, delaying the change by 0.3 seconds. Its top location also undergoes a precise time effect, transitioning from a cubic-bezier interpolation for 0.4 seconds.
- The color given by the variable “loadingColor” fills the SVG icon within the “Loading Message”. It is set to a width of 22 pixels and has no margin.
Success Message Style
.successMessage svg {
color: var(--successColor);
stroke-dasharray: 20;
stroke-dashoffset: 20;
transition: stroke-dashoffset 0.3s ease-in-out;
width: 14px;
}
Here we define
- The “Success Message” expands leftward, positioned five pixels from the left, and uses the CSS character “dr” to specify the timing function for the change from hidden to visible.
- The color supplied by the variable “successColor” fills the SVG icon within the “Success Message”. When using an ease-in-out timing function, a stroke-dashoffset transition takes longer than 0.3 seconds. Initially, both stroke-dasharray and stroke-dashoffset are set to 20.
Animation Delay
.loadingCircle:nth-child(2) {
animation-delay: 0.1s;
}
.loadingCircle:nth-child(3) {
animation-delay: 0.2s;
}
In order to produce a sequential effect, we apply animation delays for particular loading circles in this particular case. This makes the loading animation appear sequential.
JavaScript
To make an interactive confetti button, we can separate the JavaScript code into its various parts.
Let’s start –
Initialization and Constants
Constant confettiCount = 20
Constant sequenceCount = 10
Constant gravityConfetti = 0.3
Constant gravitySequence = 0.55
Constant dragConfetti = 0.075
Constant dragSequence = 0.02
Constant terminalVelocity = 3
Here, we define constant variables that establish the confetti and sequence start setups. Here, a number of physical-related factors are defined, including gravity, drag, and terminal velocity for both sequence and confetti.
Global Elements and Canvas Setup
const button = document.getElementById('button')
var disabled = false
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
canvas.width = window.innerWidth
canvas.height = window.innerHeight
let cx = ctx.canvas.width / 2
let cy = ctx.canvas.height / 2
Global components like the button and canvas are initialized here. After sizing the canvas to fill the window, a drawing reference (ctx) is acquired.
Confetti and Sequin Setup
let confetti = []
let sequins = []
const colors = [
{ front : '#7b5cff', back: '#6245e0' }, // Purple
{ front : '#b3c7ff', back: '#8fa5e5' }, // Light Blue
{ front : '#5c86ff', back: '#345dd1' } // Darker Blue
]
In this case, instances of the confetti and sequence objects are stored in arrays called “confetti” and “sequence,” respectively. Furthermore, a color spectrum is specified, outlining the forward and backward colors for confetti.
Helper Function
randomRange = (min, max) => Math.random() * (max - min) + min
// helper function to get initial velocities for confetti
// this weighted spread helps the confetti look more realistic
initConfettoVelocity = (xRange, yRange) => {
const x = randomRange(xRange[0], xRange[1])
const range = yRange[1] - yRange[0] + 1
let y = yRange[1] - Math.abs(randomRange(0, range) + randomRange(0, range) - range)
if (y >= yRange[1] - 1) {
// Occasional confetto goes higher than the max
y += (Math.random() < .25) ? randomRange(1, 3) : 0
}
return {x: x, y: -y}
}
Helper functions are defined here. While “initConfettoVelocity” calculates initial velocities for confetti and offers charged spread for a more realistic presence, “randomRange” generates a random number within a defined range.
Confetto Class
function Confetto() {
this.randomModifier = randomRange(0, 99)
this.color = colors[Math.floor(randomRange(0, colors.length))]
this.dimensions = {
x: randomRange(5, 9),
y: randomRange(8, 15),
}
this.position = {
x: randomRange(canvas.width/2 - button.offsetWidth/4, canvas.width/2 + button.offsetWidth/4),
y: randomRange(canvas.height/2 + button.offsetHeight/2 + 8, canvas.height/2 + (1.5 * button.offsetHeight) - 8),
}
this.rotation = randomRange(0, 2 * Math.PI)
this.scale = {
x: 1,
y: 1,
}
this.velocity = initConfettoVelocity([-9, 9], [6, 11])
}
Confetto.prototype.update = function() {
// apply forces to velocity
this.velocity.x -= this.velocity.x * dragConfetti
this.velocity.y = Math.min(this.velocity.y + gravityConfetti, terminalVelocity)
this.velocity.x += Math.random() > 0.5 ? Math.random() : -Math.random()
// set position
this.position.x += this.velocity.x
this.position.y += this.velocity.y
// spin confetto by scaling y and set the color, .09 just slows cosine frequency
this.scale.y = Math.cos((this.position.y + this.randomModifier) * 0.09)
}
For the Confetto class, we define a constructor function here. Individual confetti pieces are represented by instances of Confetto, each of which has unique features like state, color, size, rotation, and velocity. The update approach takes care of the rotation and velocity aspects of confetti physics.
Sequin Class
function Sequin() {
this.color = colors[Math.floor(randomRange(0, colors.length))].back,
this.radius = randomRange(1, 2),
this.position = {
x: randomRange(canvas.width/2 - button.offsetWidth/3, canvas.width/2 + button.offsetWidth/3),
y: randomRange(canvas.height/2 + button.offsetHeight/2 + 8, canvas.height/2 + (1.5 * button.offsetHeight) - 8),
},
this.velocity = {
x: randomRange(-6, 6),
y: randomRange(-8, -12)
}
}
Sequin.prototype.update = function() {
// apply forces to velocity
this.velocity.x -= this.velocity.x * dragSequins
this.velocity.y = this.velocity.y + gravitySequins
// set position
this.position.x += this.velocity.x
this.position.y += this.velocity.y
}
The Sequence class has a constructor function, just like Confetto. Sequence instances are individual sequins, each of which is a unique sequin with characteristics like color, state, velocity, and radius. The update mechanism takes care of the sequins’ mechanics, including their speed.
Initialization Function
initBurst = () => {
for (let i = 0; i < confettiCount; i++) {
confetti.push(new Confetto())
}
for (let i = 0; i < sequinCount; i++) {
sequins.push(new Sequin())
}
}
Render Function
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)
// update confetto "physics" values
confetto.update()
// get front or back fill color
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)
// clear rectangle where button cuts off
if (confetto.velocity.y < 0) {
ctx.clearRect(canvas.width/2 - button.offsetWidth/2, canvas.height/2 + button.offsetHeight/2, button.offsetWidth, button.offsetHeight)
}
})
sequins.forEach((sequin, index) => {
// move canvas to position
ctx.translate(sequin.position.x, sequin.position.y)
// update sequin "physics" values
sequin.update()
// set the color
ctx.fillStyle = sequin.color
// draw sequin
ctx.beginPath()
ctx.arc(0, 0, sequin.radius, 0, 2 * Math.PI)
ctx.fill()
// reset transform matrix
ctx.setTransform(1, 0, 0, 1, 0, 0)
// clear rectangle where button cuts off
if (sequin.velocity.y < 0) {
ctx.clearRect(canvas.width/2 - button.offsetWidth/2, canvas.height/2 + button.offsetHeight/2, button.offsetWidth, button.offsetHeight)
}
})
// remove confetti and sequins that fall off the screen
// must be done in seperate loops to avoid noticeable flickering
confetti.forEach((confetto, index) => {
if (confetto.position.y >= canvas.height) confetti.splice(index, 1)
})
sequins.forEach((sequin, index) => {
if (sequin.position.y >= canvas.height) sequins.splice(index, 1)
})
window.requestAnimationFrame(render)
}
Here, we define the “render” function that is in responsible for rendering sequins and confetti onto the canvas. It creates dynamic animations by utilizing the HTML5 canvas API. The function displays the sequins and confetti after clearing the canvas and updating their positions. It uses rotations and modifications to create a visually pleasing effect.
Button Click Function
clickButton = () => {
if (!disabled) {
disabled = true
// Loading stage
button.classList.add('loading')
button.classList.remove('ready')
setTimeout(() => {
// Completed stage
button.classList.add('complete')
button.classList.remove('loading')
setTimeout(() => {
window.initBurst()
setTimeout(() => {
// Reset button so user can select it again
disabled = false
button.classList.add('ready')
button.classList.remove('complete')
}, 4000)
}, 320)
}, 1800)
}
}
Let’s define the function called “clickButton” now. This function mimics the event of a button click. The button is initially set to the loading state, then it moves through to the complete state, starts the confetti explosion, and then it resets itself to await user input.
Canvas Resize Function
resizeCanvas = () => {
canvas.width = window.innerWidth
canvas.height = window.innerHeight
cx = ctx.canvas.width / 2
cy = ctx.canvas.height / 2
}
In order to modify the canvas size as the window resizes, we define the “resizeCanvas” function here.
Event Listeners
window.addEventListener('resize', () => {
resizeCanvas()
})
document.body.onkeyup = (e) => {
if (e.keyCode == 13 || e.keyCode == 32) {
clickButton()
}
}
Key presses and window resizing are handled by these event listeners. While the “clickButton” function is triggered by clicking the space bar or Enter, the “resizeCanvas” function activates when the window size is changed.
Text Transition Setup
textElements = button.querySelectorAll('.button-text')
textElements.forEach((element) => {
characters = element.innerText.split('')
let characterHTML = ''
characters.forEach((letter, index) => {
characterHTML += `${letter}`
})
element.innerHTML = characterHTML
})
The button text’s transition times between characters are set in this area to produce a visually appealing text effect.
Text Transition Setup
window.initBurst()
render()
Here, we define the script. “initBurst” is used to start the confetti and sequins, and render is used to start the animation loop. Confetti and sequins are constantly updated and shown on the canvas by the animation, creating a visually pleasing scene.
Output
See the Pen Confetti Button by Cooper Goeke (@coopergoeke) on CodePen.
You can visit our Codepen profile for source codes.
Conclusion
We’re going to finish this project with a fantastic confetti button that will encourage users to click and interact even more.
1 thought on “Confetti Button Using JavaScript”
Pingback: Create Glow Flicker Using CSS - cssiseasy