Tilt Effect
Tilt Effect is an interactive component that creates a perspective effect by making buttons, cards or images respond to mouse movements.

Red Hat
Best Sellers
import Image from "next/image";
import { TiltEffect } from "@/components/core/tilt-effect";
import { Card, CardContent } from "@/components/ui/card";
export default function TiltEffectExample() {
return (
<TiltEffect>
<Card className="overflow-hidden pt-0 md:w-[280px]">
<Image
src="https://bundui-images.netlify.app/products/04.jpeg"
alt=""
width={200}
height={200}
className="aspect-square size-full w-full object-cover transition-all duration-200 ease-linear"
unoptimized
/>
<CardContent>
<div className="text-xl">Red Hat</div>
<div className="text-muted-foreground text-sm">Best Sellers</div>
</CardContent>
</Card>
</TiltEffect>
);
}
Installation
Install the following dependencies:
npm install motion
Copy and paste the following code into your project:
"use client";
import React, { useState, useRef, useEffect } from "react";
import { motion, useMotionValue, useSpring, useTransform } from "motion/react";
export type TiltEffectProps = {
children: React.ReactNode;
tiltFactor?: number;
perspective?: number;
transitionDuration?: number;
};
export const TiltEffect: React.FC<TiltEffectProps> = ({
children,
tiltFactor = 12,
perspective = 1000,
transitionDuration = 0.5
}) => {
const ref = useRef<HTMLDivElement>(null);
const [elementSize, setElementSize] = useState({ width: 0, height: 0 });
const x = useMotionValue(0);
const y = useMotionValue(0);
const springConfig = { damping: 30, stiffness: 400, mass: 0.5 };
const xSpring = useSpring(x, springConfig);
const ySpring = useSpring(y, springConfig);
const rotateX = useTransform(
ySpring,
[-elementSize.height / 2, elementSize.height / 2],
[tiltFactor, -tiltFactor]
);
const rotateY = useTransform(
xSpring,
[-elementSize.width / 2, elementSize.width / 2],
[-tiltFactor, tiltFactor]
);
useEffect(() => {
const updateSize = () => {
if (ref.current) {
setElementSize({
width: ref.current.offsetWidth,
height: ref.current.offsetHeight
});
}
};
updateSize();
window.addEventListener("resize", updateSize);
return () => window.removeEventListener("resize", updateSize);
}, []);
const handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {
if (!ref.current) return;
const rect = ref.current.getBoundingClientRect();
const mouseX = event.clientX - rect.left;
const mouseY = event.clientY - rect.top;
const centerX = elementSize.width / 2;
const centerY = elementSize.height / 2;
x.set(mouseX - centerX);
y.set(mouseY - centerY);
};
const handleMouseLeave = () => {
x.set(0);
y.set(0);
};
return (
<motion.div
ref={ref}
style={{
perspective,
transformStyle: "preserve-3d"
}}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}>
<motion.div
style={{
rotateX,
rotateY
}}
transition={{
duration: transitionDuration,
type: "spring",
...springConfig
}}>
{children}
</motion.div>
</motion.div>
);
};
Update the import paths to match your project setup.
Usage
import Image from "next/image";
import { TiltEffect } from "@/components/core/tilt-effect";
import { Card, CardContent } from "@/components/ui/card";
export default function TiltEffectExample() {
return (
<TiltEffect>
<Card className="overflow-hidden pt-0 md:w-[280px]">
<Image
src="https://bundui-images.netlify.app/products/04.jpeg"
alt=""
width={200}
height={200}
className="aspect-square size-full w-full object-cover transition-all duration-200 ease-linear"
unoptimized
/>
<CardContent>
<div className="text-xl">Red Hat</div>
<div className="text-muted-foreground text-sm">Best Sellers</div>
</CardContent>
</Card>
</TiltEffect>
);
}
Props
Prop | Type | Default |
---|---|---|
transitionDuration? | number | 0.5 |
perspective? | number | 1000 |
tiltFactor? | number | 12 |
children | ReactNode | - |