ReactHustle

How to create a ripple effect in react

Jasser Mark Arioste

Jasser Mark Arioste

How to create a ripple effect in react

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:

Error:  The Parser function of type "raw" is not defined. Define your custom parser functions as: https://github.com/pavittarx/editorjs-html#extend-for-custom-blocks 

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.

  1. Button with position:relative and overflow:hidden
  2. a ripple keyframe css
  3. 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:

Simple button with no functionaltiy

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

Share this post!

Related Posts

Disclaimer

This content may contain links to products, software and services. Please assume all such links are affiliate links which may result in my earning commissions and fees.
As an Amazon Associate, I earn from qualifying purchases. This means that whenever you buy a product on Amazon from a link on our site, we receive a small percentage of its price at no extra cost to you. This helps us continue to provide valuable content and reviews to you. Thank you for your support!
Donate to ReactHustle