Button ripple effect

by phloe

HTML

<button id="button">Click here</button>

CSS

button {
  --mouse-x: 0px;
  --mouse-y: 0px;
  padding: 10px 20px;
  border: 0;
  color: #FFF;
  background: red;
  text-transform: uppercase;
  position: relative;
  overflow: hidden;
  box-sizing: border-box;
}

button::after {
  content: "";
  display: block;
  position: absolute;
  top: -15px;
  left: -15px;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background-color: #FFF;
  opacity: 0;
  transform: translate(var(--mouse-x, 0px), var(--mouse-y, 0px)) scale(0.5);
}

button:focus:not(:focus-visible) {
  outline: none;
}

button:active::after {
  opacity: 1;
  transition: 0.2s opacity ease-in-out;
}

.ripple::after {
  animation: 0.75s button  ease-in-out;
}

@keyframes button {
  0% {
    opacity: 0.5;
  }
  100% {
    transform: translate(var(--mouse-x, 0px), var(--mouse-y, 0px)) scale(10);
    opacity: 0;
  }
}

JavaScript

let rippling = false;
function resetRipple() {
	button.removeEventListener("animationend", resetRipple);
	button.classList.remove("ripple");
  rippling = false;
}

button.addEventListener("mousedown", function(event) {
	if (rippling) {
  	resetRipple();
    void button.offsetWidth;
  }
  
  rippling = true;
	const { left, top } = button.getBoundingClientRect();
  button.style.setProperty("--mouse-x", event.clientX - left + "px");
  button.style.setProperty("--mouse-y", event.clientY - top + "px");
  button.addEventListener("animationend", resetRipple);
  button.classList.add("ripple");
});