Wobble Card
A card effect that translates and scales on mousemove, perfect for feature cards.
Gippity AI powers the entire universe
With over 100,000 mothly active bot users, Gippity AI is the most popular AI platform for developers.
No shirt, no shoes, no weapons.
If someone yells “stop!”, goes limp, or taps out, the fight is over.
Signup for blazing-fast cutting-edge state of the art Gippity AI wrapper today!
With over 100,000 mothly active bot users, Gippity AI is the most popular AI platform for developers.
Installation
Install dependencies
npm i framer-motion clsx tailwind-merge
Add util file
lib/utils.ts
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Download noise.webp
Download noise.webp
from this link and paste it in the public
folder.
Copy the source code
components/ui/wobble-card.tsx
"use client";
import React, { useState } from "react";
import { motion } from "framer-motion";
import { cn } from "@/lib/utils";
export const WobbleCard = ({
children,
containerClassName,
className,
}: {
children: React.ReactNode;
containerClassName?: string;
className?: string;
}) => {
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
const [isHovering, setIsHovering] = useState(false);
const handleMouseMove = (event: React.MouseEvent<HTMLElement>) => {
const { clientX, clientY } = event;
const rect = event.currentTarget.getBoundingClientRect();
const x = (clientX - (rect.left + rect.width / 2)) / 20;
const y = (clientY - (rect.top + rect.height / 2)) / 20;
setMousePosition({ x, y });
};
return (
<motion.section
onMouseMove={handleMouseMove}
onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => {
setIsHovering(false);
setMousePosition({ x: 0, y: 0 });
}}
style={{
transform: isHovering
? `translate3d(${mousePosition.x}px, ${mousePosition.y}px, 0) scale3d(1, 1, 1)`
: "translate3d(0px, 0px, 0) scale3d(1, 1, 1)",
transition: "transform 0.1s ease-out",
}}
className={cn(
"mx-auto w-full bg-indigo-800 relative rounded-2xl overflow-hidden",
containerClassName
)}
>
<div
className="relative h-full [background-image:radial-gradient(88%_100%_at_top,rgba(255,255,255,0.5),rgba(255,255,255,0))] sm:mx-0 sm:rounded-2xl overflow-hidden"
style={{
boxShadow:
"0 10px 32px rgba(34, 42, 53, 0.12), 0 1px 1px rgba(0, 0, 0, 0.05), 0 0 0 1px rgba(34, 42, 53, 0.05), 0 4px 6px rgba(34, 42, 53, 0.08), 0 24px 108px rgba(47, 48, 55, 0.10)",
}}
>
<motion.div
style={{
transform: isHovering
? `translate3d(${-mousePosition.x}px, ${-mousePosition.y}px, 0) scale3d(1.03, 1.03, 1)`
: "translate3d(0px, 0px, 0) scale3d(1, 1, 1)",
transition: "transform 0.1s ease-out",
}}
className={cn("h-full px-4 py-20 sm:px-10", className)}
>
<Noise />
{children}
</motion.div>
</div>
</motion.section>
);
};
const Noise = () => {
return (
<div
className="absolute inset-0 w-full h-full scale-[1.2] transform opacity-10 [mask-image:radial-gradient(#fff,transparent,75%)]"
style={{
backgroundImage: "url(/noise.webp)",
backgroundSize: "30%",
}}
></div>
);
};
Props
Prop Name | Type | Required | Description |
---|---|---|---|
children | React.ReactNode | Yes | Content to be rendered inside the WobbleCard. |
containerClassName | string | No | Optional className for styling the container. |
className | string | No | Optional className for styling the children wrapper. |