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;
1234567891011121314151617181920212223242526
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; } }
1234567
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
1
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;
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
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;
1234567891011121314151617181920212223242526272829
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