Ripple Effect
Ripple effect is a modern animation component designed to enrich user interaction by creating a dynamic ripple effect when clicking on buttons or any HTML element. Built with Tailwind CSS and Motion.
import { RippleEffect } from "@/components/core/ripple-effect";
import { Button } from "@/components/ui/button";
export default function RippleEffectButton() {
return (
<RippleEffect className="rounded-lg">
<Button size="lg">Click me</Button>
</RippleEffect>
);
}
Installation
Install the following dependencies:
npm install motion
Copy and paste the following code into your project:
"use client";
import React from "react";
import { motion, AnimatePresence } from "motion/react";
interface RippleProps {
children: React.ReactNode;
color?: string;
duration?: number;
className?: string;
}
interface RipplePosition {
x: number;
y: number;
size: number;
id: number;
}
export const RippleEffect = ({
children,
color = "rgba(255, 255, 255, 0.7)",
duration = 0.8,
className,
}: RippleProps) => {
const [ripples, setRipples] = React.useState<RipplePosition[]>([]);
const containerRef = React.useRef<HTMLDivElement>(null);
const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
if (!containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const size = Math.max(rect.width, rect.height);
const newRipple: RipplePosition = {
x,
y,
size: size * 2,
id: Date.now(),
};
setRipples((prevRipples) => [...prevRipples, newRipple]);
setTimeout(() => {
setRipples((prevRipples) =>
prevRipples.filter((ripple) => ripple.id !== newRipple.id),
);
}, duration * 1000);
};
return (
<div
ref={containerRef}
className={`inline-flex relative overflow-hidden rounded-lg ${className}`}
onMouseDown={handleClick}
>
{children}
<AnimatePresence>
{ripples.map((ripple) => (
<motion.div
key={ripple.id}
initial={{
width: 0,
height: 0,
x: ripple.x,
y: ripple.y,
opacity: 0.5,
borderRadius: "100%",
}}
animate={{
width: ripple.size,
height: ripple.size,
x: ripple.x - ripple.size / 2,
y: ripple.y - ripple.size / 2,
opacity: 0,
borderRadius: "100%",
}}
exit={{ opacity: 0 }}
transition={{ duration, ease: "easeOut" }}
style={{
position: "absolute",
backgroundColor: color,
pointerEvents: "none",
}}
/>
))}
</AnimatePresence>
</div>
);
};
Update the import paths to match your project setup.
Usage
import { RippleEffect } from "@/components/core/ripple-effect";
import { Button } from "@/components/ui/button";
export default function RippleEffectButton() {
return (
<RippleEffect className="rounded-lg">
<Button size="lg">Click me</Button>
</RippleEffect>
);
}
Examples
Colored
Blue
Green
Purple
Red
import { RippleEffect } from "@/components/core/ripple-effect";
export default function RippleEffectColored() {
return (
<div className="grid grid-cols-2 gap-4">
<div>
<RippleEffect className="block" color="var(--color-blue-400)">
<div className="w-36 bg-blue-200 p-2 text-center text-blue-800">Blue</div>
</RippleEffect>
</div>
<div>
<RippleEffect className="block" color="var(--color-green-400)">
<div className="w-36 bg-green-200 p-2 text-center text-green-800">Green</div>
</RippleEffect>
</div>
<div>
<RippleEffect className="block" color="var(--color-purple-400)">
<div className="w-36 bg-purple-200 p-2 text-center text-purple-800">Purple</div>
</RippleEffect>
</div>
<div>
<RippleEffect className="block" color="var(--color-red-400)">
<div className="w-36 bg-red-200 p-2 text-center text-red-800">Red</div>
</RippleEffect>
</div>
</div>
);
}
Image


import Image from "next/image";
import { RippleEffect } from "@/components/core/ripple-effect";
export default function RippleEffectImage() {
return (
<div className="flex gap-4">
<RippleEffect color="var(--color-black)">
<figure className="relative aspect-square w-40">
<Image
src="https://bundui-images.netlify.app/products/06.jpeg"
fill
alt="ripple effect image"
/>
</figure>
</RippleEffect>
<RippleEffect color="var(--color-purple-600)">
<figure className="relative aspect-square w-40">
<Image
src="https://bundui-images.netlify.app/products/07.jpeg"
fill
alt="ripple effect image"
/>
</figure>
</RippleEffect>
</div>
);
}
Props
Prop | Type | Default |
---|---|---|
className? | string | - |
duration? | number | 0.8 |
color? | string | "rgba(255, 255, 255, 0.7)" |
children | React.ReactNode | - |