here's the example code that looks similar to the typescript
unlike other ripple button with this link example
[https://css-tricks.com/how-to-recreate-the-ripple-effect-of-material-design-buttons/][1]
but with this code it's use the keyboard mouse and touch to start ripple effect on your button
here's the full code html with javascript and css
function addRippleEffect(button) {
const buttonColor = button.getAttribute('data-button-color');
const hoverColor = button.getAttribute('data-hover-color');
const textColor = button.getAttribute('data-text-color');
const overlay = button.querySelector('.hover-overlay');
if (buttonColor) {
button.style.backgroundColor = buttonColor;
}
if (textColor) {
button.style.color = textColor;
}
if (hoverColor) {
overlay.style.backgroundColor = hoverColor;
}
const ripples = new Set();
let isInteractionActive = false;
let initialTouchX = 0;
let initialTouchY = 0;
const moveThreshold = 10;
function setHoverState() {
overlay.style.opacity = '0.3';
}
function resetState() {
overlay.style.opacity = '0';
}
button.addEventListener('mouseenter', setHoverState);
button.addEventListener('focus', setHoverState);
button.addEventListener('mouseleave', () => {
resetState();
fadeOutAllRipples();
});
function createRipple(e) {
let x, y;
const rect = button.getBoundingClientRect();
const size = Math.max(rect.width, rect.height);
if (e instanceof KeyboardEvent) {
x = rect.width / 2 - size / 2;
y = rect.height / 2 - size / 2;
} else if (e.type === 'touchstart') {
e.preventDefault();
initialTouchX = e.touches[0].clientX;
initialTouchY = e.touches[0].clientY;
setHoverState();
x = e.touches[0].clientX - rect.left - size / 2;
y = e.touches[0].clientY - rect.top - size / 2;
} else {
x = e.clientX - rect.left - size / 2;
y = e.clientY - rect.top - size / 2;
}
const ripple = document.createElement('span');
ripple.classList.add('ripple');
ripple.style.width = size + 'px';
ripple.style.height = size + 'px';
ripple.style.left = x + 'px';
ripple.style.top = y + 'px';
ripple.style.background = button.getAttribute('data-ripple-color');
button.appendChild(ripple);
ripples.add(ripple);
isInteractionActive = true;
let scale = 0;
const scaleIncrement = 0.1 * (100 / size);
function animate() {
if (!ripples.has(ripple)) return;
scale += scaleIncrement;
ripple.style.transform = `scale(${scale})`;
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
}
function fadeOutRipple(ripple) {
if (ripples.has(ripple)) {
ripple.classList.add('fade-out');
ripple.addEventListener('animationend', () => {
ripple.remove();
ripples.delete(ripple);
}, { once: true });
}
}
function fadeOutAllRipples() {
isInteractionActive = false;
ripples.forEach(fadeOutRipple);
resetState();
}
button.addEventListener('mousedown', createRipple);
button.addEventListener('mouseup', () => {
if (isInteractionActive && button.getAttribute('href')) {
button.click();
}
fadeOutAllRipples();
});
button.addEventListener('mouseleave', fadeOutAllRipples);
button.addEventListener('touchstart', createRipple, { passive: false });
button.addEventListener('touchend', () => {
if (isInteractionActive && button.getAttribute('href')) {
button.click();
}
fadeOutAllRipples();
});
button.addEventListener('touchcancel', fadeOutAllRipples);
button.addEventListener('touchmove', (e) => {
const touch = e.touches[0];
const rect = button.getBoundingClientRect();
const dx = Math.abs(touch.clientX - initialTouchX);
const dy = Math.abs(touch.clientY - initialTouchY);
if (dx > moveThreshold || dy > moveThreshold) {
fadeOutAllRipples();
}
if (
touch.clientX < rect.left ||
touch.clientX > rect.right ||
touch.clientY < rect.top ||
touch.clientY > rect.bottom
) {
fadeOutAllRipples();
}
}, { passive: true });
button.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
setHoverState();
createRipple(e);
}
});
button.addEventListener('keyup', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
if (isInteractionActive && button.getAttribute('href')) {
button.click();
}
fadeOutAllRipples();
}
});
button.addEventListener('blur', fadeOutAllRipples);
button.addEventListener('click', (e) => {
if (!isInteractionActive) {
e.preventDefault();
e.stopPropagation();
}
});
}
document.querySelectorAll('a.button').forEach(button => {
addRippleEffect(button);
});
a.button {
display: inline-block;
padding: 12px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
font-family: Arial, sans-serif;
text-decoration: none;
text-align: center;
position: relative;
overflow: hidden;
outline: none;
margin: 10px;
}
a.button:focus-visible {
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.3);
}
.hover-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
transition: opacity 0.3s ease;
z-index: 1;
}
.ripple {
position: absolute;
border-radius: 50%;
pointer-events: none;
opacity: 0.5;
transform: scale(0);
z-index: 2;
}
.ripple.fade-out {
animation: fadeOut 0.5s ease forwards;
}
@keyframes fadeOut {
to {
opacity: 0;
}
}
<a class="button"
data-ripple-color="rgba(0, 0, 0, 0.5)"
data-button-color="rgb(255, 102, 102)"
data-hover-color="rgb(255, 51, 51)"
data-text-color="rgb(255, 255, 255)">
<span class="hover-overlay"></span>
Red Ripple
</a>
<a href="https://example.com/blue-page" class="button"
data-ripple-color="rgba(0, 0, 0, 0.5)"
data-button-color="rgb(102, 102, 255)"
data-hover-color="rgb(51, 51, 255)"
data-text-color="rgb(255, 255, 255)">
<span class="hover-overlay"></span>
Blue Ripple
</a>
<a href="https://example.com/green-page" class="button"
data-ripple-color="rgba(0, 0, 0, 0.5)"
data-button-color="rgb(102, 255, 102)"
data-hover-color="rgb(51, 255, 51)"
data-text-color="rgb(0, 0, 0)">
<span class="hover-overlay"></span>
Green Ripple
</a>