3D Tilt Effect

The 3D Tilt Effect is an interactive visual technique that makes cards or images respond to mouse movements, creating a perspective effect. When a user moves their cursor over the card, it appears to tilt as if it were a 3D object. This effect is achieved through mouse tracking and perspective transform techniques, allowing the card to subtly bend in response to the user’s actions.

Zoe Garcia
Backend Developer

Installation

Install the following dependencies:

npm i framer-motion clsx tailwind-merge

Add util file

import { ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Copy and paste the following code into your project.

"use client";

import React, { useRef } from "react";
import {
  motion,
  useMotionTemplate,
  useMotionValue,
  useSpring,
} from "framer-motion";
import { cn } from "@/lib/utils";

type Props = { children: React.ReactNode; className?: string };

const ROTATION_RANGE = 32.5;
const HALF_ROTATION_RANGE = 32.5 / 2;

export function Three3DTiltEffect({ children, className }: Props) {
  const ref = useRef<HTMLDivElement>(null);

  const x = useMotionValue(0);
  const y = useMotionValue(0);

  const xSpring = useSpring(x);
  const ySpring = useSpring(y);

  const transform = useMotionTemplate`rotateX(${xSpring}deg) rotateY(${ySpring}deg)`;

  const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
    if (!ref.current) return [0, 0];

    const rect = ref.current.getBoundingClientRect();

    const width = rect.width;
    const height = rect.height;

    const mouseX = (e.clientX - rect.left) * ROTATION_RANGE;
    const mouseY = (e.clientY - rect.top) * ROTATION_RANGE;

    const rX = (mouseY / height - HALF_ROTATION_RANGE) * -1;
    const rY = mouseX / width - HALF_ROTATION_RANGE;

    x.set(rX);
    y.set(rY);
  };

  const handleMouseLeave = () => {
    x.set(0);
    y.set(0);
  };

  return (
    <motion.div
      ref={ref}
      onMouseMove={handleMouseMove}
      onMouseLeave={handleMouseLeave}
      style={{
        transformStyle: "preserve-3d",
        transform,
      }}
      className={cn("w-72", className)}
    >
      {children}
    </motion.div>
  );
}

Usage

import { Three3DTiltEffect } from "@/components/ui/3d-tilt-effect";
import Image from "next/image";

export default function ThreeDTiltEffectExample() {
  return (
    <>
      <Three3DTiltEffect>
        <div className="bg-background rounded-xl overflow-hidden border group/hoverimg shadow-lg">
          <div className="h-full overflow-hidden">
            <Image
              src="https://cosmic.shadcnuikit.com/_next/image?url=https%3A%2F%2Fimages.unsplash.com%2Fphoto-1573497019940-1c28c88b4f3e%3Fq%3D80%26w%3D1587%26auto%3Dformat%26fit%3Dcrop%26ixlib%3Drb-4.0.3%26ixid%3DM3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%253D%253D&w=384&q=75"
              alt=""
              width={300}
              height={300}
              className="w-full aspect-square object-cover transition-all duration-200 ease-linear size-full"
            />
          </div>
          <div className="flex flex-col py-6 px-6">
            <div className="text-xl">Zoe Garcia</div>
            <span className="text-muted-foreground">Backend Developer</span>
          </div>
        </div>
      </Three3DTiltEffect>
    </>
  );
}