How to create a ripple effect in react
Jasser Mark Arioste
In one of my projects, I needed a way to add ripple effect to buttons since I was using a UI library with no ripple effect. In this tutorial, I’ll show you how to add a ripple effect using react hooks. The final result can be previewed below:
There are a couple of things to note. First is we will will be using typescript in this tutorial. Lastly, we will only have one dependency which is usehooks-ts since we will be using the useDebounce hook later on to remove the ripples after a few seconds has passed.
Steps to create a ripple effect
there are three main things that you need when creating ripple effect in react.
- Button with position:relative and overflow:hidden
- a ripple keyframe css
- a react hook that creates ripples: useRipple hook
Step 1: Button with position relative
Let’s create a simple button component for now. well add the functionality later. If you’re using css libraries like tailwindcss or daisyUI, you can use the classes to create a button as well
#components/Button.tsx
import React, { useRef } from "react";
const Button = () => {
return (
<button
style={{
padding: 16,
backgroundColor: "#1e293b",
color: "white",
border: "none",
borderRadius: 8,
cursor: "pointer",
position: "relative",
overflow: "hidden",
}}
>
Button
</button>
);
};
export default Button;
Output:
Step 2: Add a @keyframe ripple css
Let’s add this to our css file. Basically, you just need to scale and reduce the opacity to 0.
#style.css
@keyframes ripple {
to {
transform: scale(4);
opacity: 0;
}
}
Step 3: Create a useRipple hook.
Let’s now create the useRipple hook that gives life to our button. But first we’ll install usehooks-ts
yarn add usehooks-ts
Create a file called useRipple.tsx . Please read through the comments because they are important!
//hooks/useRipple.tsx
import React, { useEffect, useState } from "react";
import { useDebounce } from "usehooks-ts";
/**
* This hook accepts a ref to any element and adds a click event handler that creates ripples when click
*/
const useRipple = <T extends HTMLElement>(ref: React.RefObject<T>) => {
//ripples are just styles that we attach to span elements
const [ripples, setRipples] = useState<React.CSSProperties[]>([]);
useEffect(() => {
//check if there's a ref
if (ref.current) {
const elem = ref.current;
//add a click handler for the ripple
const clickHandler = (e: MouseEvent) => {
//calculate the position and dimensions of the ripple.
//based on click position and button dimensions
var rect = elem.getBoundingClientRect();
var left = e.clientX - rect.left;
var top = e.clientY - rect.top;
const height = elem.clientHeight;
const width = elem.clientWidth;
const diameter = Math.max(width, height);
setRipples([
...ripples,
{
top: top - diameter / 2,
left: left - diameter / 2,
height: Math.max(width, height),
width: Math.max(width, height),
},
]);
};
//add an event listener to the button
elem.addEventListener("click", clickHandler);
//clean up when the component is unmounted
return () => {
elem.removeEventListener("click", clickHandler);
};
}
}, [ref, ripples]);
//add a debounce so that if the user doesn't click after 1s, we remove the ripples
const _debounced = useDebounce(ripples, 1000);
useEffect(() => {
if (_debounced.length) {
setRipples([]);
}
}, [_debounced.length]);
//map through the ripples and return span elements.
//this will be added to the button component later
return ripples?.map((style, i) => {
return (
<span
key={i}
style={{
...style,
//should be absolutely positioned
position: "absolute",
backgroundColor: "#FFFFFF",
opacity: "25%",
transform: "scale(0)",
// add ripple animation from styles.css
animation: "ripple 600ms linear",
borderRadius: "50%",
}}
/>
);
});
};
export default useRipple;
Step 4: Use the useRipple hook on our button component
Here’s how to use it in our button component:
import React, { useRef } from "react";
import useRipple from "./useRipple";
const Button = () => {
//create a ref to reference the button
const ref = useRef<HTMLButtonElement>(null);
//pass the ref to the useRipple hook
const ripples = useRipple(ref);
return (
<button
ref={ref}
style={{
padding: 16,
backgroundColor: "#1e293b",
color: "white",
border: "none",
borderRadius: 8,
cursor: "pointer",
position: "relative",
overflow: "hidden",
}}
>
{ripples}
Button
</button>
);
};
export default Button;
With this we now have ripple effect within our buttons.
Conclusion
We have successfully implemented a ripple effect using useRipple hook and we can use it in any element, and integrate it with any css library.
Hopefully you had fun during this tutorial! If you like tutorials like this in react, please subscribe to my newsletter down below to keep updated for future posts!
Credits: Image by Joe from Pixabay


