This particular animation effect is commonly referred to as the Material ripple effect, or at least that's how most people tend to identify it.
There are two distinct approaches to achieving this effect - one involves a combination of JS and CSS for a comprehensive ripple effect, where the ripple originates from the position of the mouse, while the other method relies solely on CSS without any JavaScript involved, resulting in the ripple emanating from the button regardless of the mouse's location within it.
While some individuals prefer the cleaner CSS-only approach, the majority lean towards the full-fledged version due to its consideration of mouse positioning, thereby delivering a slightly enhanced user experience...
In any case, I have developed both these effects, so feel free to choose the one that suits your preference :).
PS: Here are the guidelines applicable to any full-fledged versions you encounter:
- The ripple should be triggered when the mouse is pressed down on the button - not upon clicking because mobile devices introduce an additional hundred milliseconds delay (as mobile browsers postpone delivering the click event to differentiate between single and double clicks). This delay before displaying the ripple significantly reduces the user experience, making your website appear slow and unresponsive even if it isn't actually sluggish.
- The ripple must remain on the button, covering its background until the mouse is released, or the button loses focus - whichever occurs first.
Without further delay, here is the provided code snippet...
window.addEventListener("mousedown", e => {
const target = e.target;
if(target.nodeName == "BUTTON" && !target.classList.contains("css-only-ripple")) {
show_ripple(target);
}
});
function show_ripple(button) {
const style = getComputedStyle(button);
let ripple_elmnt = document.createElement("span");
let diameter = Math.max(parseInt(style.height), parseInt(style.width)) * 1.5;
let radius = diameter / 2;
ripple_elmnt.className = "ripple";
ripple_elmnt.style.height = ripple_elmnt.style.width = diameter + "px";
ripple_elmnt.style.position = "absolute";
ripple_elmnt.style.borderRadius = "1000px";
ripple_elmnt.style.pointerEvents = "none";
ripple_elmnt.style.left = event.clientX - button.offsetLeft - radius + "px";
ripple_elmnt.style.top = event.clientY - button.offsetTop - radius + "px";
ripple_elmnt.style.transform = "scale(0)";
ripple_elmnt.style.transition = "transform 500ms ease, opacity 400ms ease";
ripple_elmnt.style.background = "rgba(255,255,255,0.5)";
button.appendChild(ripple_elmnt);
setTimeout(() => {
ripple_elmnt.style.transform = "scale(1)";
}, 10);
button.addEventListener("mouseup", e => {
ripple_elmnt.style.opacity = 0;
setTimeout(() => {
try {
button.removeChild(ripple_elmnt);
} catch(er) {}
}, 400);
}, {once: true});
button.addEventListener("blur", e => {
ripple_elmnt.style.opacity = 0;
setTimeout(() => {
try {
button.removeChild(ripple_elmnt);
} catch(er) {}
}, 450);
}, {once: true});
}
button {
text-transform: uppercase;
padding: 0.8em;
width: 100px;
background: #0053d9;
color: #fff;
border: none;
border-radius: 5px;
transition: all 0.2s;
font-size: 15px;
font-weight: 500;
position: relative;
overflow: hidden;
}
button:hover {
filter: brightness(80%);
cursor: pointer;
}
button:active {
transform: scale(0.92)
}
.css-only-ripple::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 150%;
aspect-ratio: 1 / 1;
transform: translate(-50%, -50%) scale(0);
pointer-events: none;
border-radius: 999px;
background: rgba(255, 255, 255, .5);
}
.css-only-ripple:focus::after {
animation: scale_up 1000ms forwards;
}
@keyframes scale_up {
0% {
transform: translate(-50%, -50%) scale(0);
opacity: 1;
}
100% {
transform: translate(-50%, -50%) scale(1);
opacity: 0;
}
}
<button>Login</button>
<button class="css-only-ripple">Login</button>
<br>
The first button indicates the CSS and JS version, while the second presents the CSS-only variant. For the CSS-only button, remember to unfocus it before clicking again to display the ripple effect (only generated upon focus)