Animated Success Button with Confetti
What is Animated Success Button with Confetti?
This is a success button, well an action button that has a loading function as well as a success message and confetti when clicked, all contained within the button.
What can i use this button for?
This could be used to give some more flair to your contact form or any other button where a loading and success maybe required.
Should I use the Animated Success Button?
A lot of code is required to give this button all of the features so you may want to consider just using a normal button for your project, but if you want to give the user a lot of feedback and animations this one could be good for your next project.
Conclusion
This button is an awesome example of animations used on one element to provide the user great feedback and a sense of a reward on clicking the button.
Original Codepen Source
Thanks to the author zanewesley, this code comes from the Animated Button pen on Codepen.
Animated Success Button with Confetti Demo
View Demo Full Screen View Demo New Tab
Animated Success Button with Confetti Code
HTML
<button id="button" class="ready" onclick="clickButton();">
<div class="message submitMessage">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 12.2">
<polyline stroke="currentColor" points="2,7.1 6.5,11.1 11,7.1 "/>
<line stroke="currentColor" x1="6.5" y1="1.2" x2="6.5" y2="10.3"/>
</svg> <span class="button-text">Click Me!</span>
</div>
<div class="message loadingMessage">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19 17">
<circle class="loadingCircle" cx="2.2" cy="10" r="1.6"/>
<circle class="loadingCircle" cx="9.5" cy="10" r="1.6"/>
<circle class="loadingCircle" cx="16.8" cy="10" r="1.6"/>
</svg>
</div>
<div class="message successMessage">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 11">
<polyline stroke="currentColor" points="1.4,5.8 5.1,9.5 11.6,2.1 "/>
</svg> <span class="button-text">Success</span>
</div>
</button>
<canvas id="canvas"></canvas>
CSS
@keyframes loading {
0% {
cy: 10;
}
25% {
cy: 3;
}
50% {
cy: 10;
}
}
body {
-webkit-font-smoothing: antialiased;
background-color: #f4f7ff;
}
canvas {
height: 100vh;
pointer-events: none;
position: fixed;
width: 100%;
z-index: 2;
}
button {
background: none;
border: none;
color: #f4f7ff;
cursor: pointer;
font-family: 'Roboto', Arial;
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;
z-index: 1;
}
button::before {
background: #1f2335;
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%;
}
button.ready .submitMessage svg {
opacity: 1;
top: 1px;
transition: top .4s ease 600ms, opacity .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 .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 .3s ease-in-out 1.4s;
}
.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;
}
.submitMessage .button-text span {
top: 8px;
transition: all 0.2s ease var(--d);
}
.submitMessage svg {
color: #5c86ff;
margin-left: -1px;
opacity: 0;
position: relative;
top: 30px;
transition: top .4s ease, opacity .3s linear;
width: 14px;
}
.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: #5c86ff;
margin: 0;
width: 22px;
}
.successMessage .button-text span {
left: 5px;
transition: all 0.2s ease var(--dr);
}
.successMessage svg {
color: #5cffa1;
stroke-dasharray: 20;
stroke-dashoffset: 20;
transition: stroke-dashoffset .3s ease-in-out;
width: 14px;
}
.loadingCircle:nth-child(2) {
animation-delay: 0.1s;
}
.loadingCircle:nth-child(3) {
animation-delay: 0.2s;
}
Javascript
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;
// add Confetti/Sequince objects to arrays to draw them
let confetti = [];
let sequins = [];
// ammount to add on each button press
const confettiCount = 20;
const sequinCount = 10;
// "physics" variables
const gravityConfetti = 0.3;
const gravitySequins = 0.55;
const dragConfetti = 0.075;
const dragSequins = 0.02;
const terminalVelocity = 3;
// colors, back side is darker for confetti flipping
const colors = [
{ front : '#7b5cff', back: '#6245e0' }, // Purple
{ front : '#b3c7ff', back: '#8fa5e5' }, // Light Blue
{ front : '#5c86ff', back: '#345dd1' } // Darker Blue
];
// helper function to pick a random number within a range
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};
}
// 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);
}
// 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;
}
// add elements to arrays to be drawn
initBurst = () => {
for (let i = 0; i < confettiCount; i++) {
confetti.push(new Confetto());
}
for (let i = 0; i < sequinCount; i++) {
sequins.push(new Sequin());
}
}
// draws the elements on the canvas
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);
}
// cycle through button states when clicked
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);
}
}
// re-init canvas if the window size changes
resizeCanvas = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
cx = ctx.canvas.width / 2;
cy = ctx.canvas.height / 2;
}
// resize listenter
window.addEventListener('resize', () => {
resizeCanvas();
});
// click button on spacebar or return keypress
document.body.onkeyup = (e) => {
if (e.keyCode == 13 || e.keyCode == 32) {
clickButton();
}
}
// Set up button text transition timings on page load
textElements = button.querySelectorAll('.button-text');
textElements.forEach((element) => {
characters = element.innerText.split('');
let characterHTML = '';
characters.forEach((letter, index) => {
characterHTML += `<span class="char${index}" style="--d:${index * 30}ms; --dr:${(characters.length - index - 1) * 30}ms;">${letter}</span>`;
})
element.innerHTML = characterHTML;
})
// kick off the render loop
render();
External Link for Animated Success Button with Confetti