<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>
@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;
}
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 CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.