Component Structure
The component uses React state to manage multiple ripples, allowing overlapping effects when the user moves their cursor across the button quickly:
const RippleButton = ({ children }: { children: React.ReactNode }) => {
const [ripples, setRipples] = useState<Array<{ id: number; x: number; y: number }>>([]);
const createRipple = (e: React.MouseEvent<HTMLButtonElement>) => {
const button = e.currentTarget;
const rect = button.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const newRipple = { id: Date.now(), x, y };
setRipples(prev => [...prev, newRipple]);
setTimeout(() => {
setRipples(prev => prev.filter(r => r.id !== newRipple.id));
}, 600);
};
return (
<button
onMouseEnter={createRipple}
className="relative overflow-hidden px-8 py-4 rounded-xl border-2 border-blue-600
text-blue-600 bg-white hover:bg-blue-600 hover:text-white
hover:scale-[102%] transition-all duration-300"
>
{ripples.map(ripple => (
<span
key={ripple.id}
className="absolute rounded-full bg-blue-500/30 animate-ripple"
style={{
left: ripple.x,
top: ripple.y,
width: '100px',
height: '100px',
marginLeft: '-50px',
marginTop: '-50px',
}}
/>
))}
<span className="relative z-10">{children}</span>
</button>
);
};
Ripple Hover Effect
The ripple effect is created on hover, not click. This makes the interaction feel softer and more exploratory—users get feedback just by moving their cursor over the button.
When the user hovers, the cursor position is calculated relative to the button's bounds, and a new ripple element is inserted at that exact location:
const createRipple = (e: React.MouseEvent<HTMLButtonElement>) => {
const button = e.currentTarget;
const rect = button.getBoundingClientRect();
const size = Math.max(rect.width, rect.height) * 2.4;
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const newRipple = { id: Date.now(), x, y };
setRipples(prev => [...prev, newRipple]);
setTimeout(() => {
setRipples(prev => prev.filter(r => r.id !== newRipple.id));
}, 600);
};
The ripple size is calculated based on the button dimensions to ensure it always covers the entire button. Each ripple is tracked in state and automatically removed after the animation completes.
Ripple Animation CSS
The ripple animation scales the element from 0 to 4x its size while fading it out, creating a smooth, water-like expansion:
@keyframes ripple {
0% {
transform: scale(0);
opacity: 1;
}
100% {
transform: scale(4);
opacity: 0;
}
}
.animate-ripple {
animation: ripple 0.6s ease-out;
}
The animation is applied using Tailwind's custom animation utilities. The pointer-events: none class ensures ripples don't interfere with button interactions.
Key Features
- Hover-triggered ripple effects for subtle, playful interaction
- Multiple overlapping ripples supported simultaneously
- Smooth color and scale transitions using Tailwind CSS
- Dynamic ripple sizing that adapts to button dimensions
- Clean state management with automatic ripple cleanup
Here's the source code, I hope you like it :)
prash240303/crafts/RippleButton