Installation
Install dependencies
npm i 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 the source code
components/ui/3d-pin.tsx
"use client";
import React, { useState } from "react";
import { motion } from "motion/react";
import { cn } from "@/lib/utils";
export const PinContainer = ({
children,
title,
href,
className,
containerClassName,
}: {
children: React.ReactNode;
title?: string;
href?: string;
className?: string;
containerClassName?: string;
}) => {
const [transform, setTransform] = useState(
"translate(-50%,-50%) rotateX(0deg)"
);
const onMouseEnter = () => {
setTransform("translate(-50%,-50%) rotateX(40deg) scale(0.8)");
};
const onMouseLeave = () => {
setTransform("translate(-50%,-50%) rotateX(0deg) scale(1)");
};
return (
<a
className={cn(
"relative group/pin z-50 cursor-pointer",
containerClassName
)}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
href={href || "/"}
>
<div
style={{
perspective: "1000px",
transform: "rotateX(70deg) translateZ(0deg)",
}}
className="absolute left-1/2 top-1/2 ml-[0.09375rem] mt-4 -translate-x-1/2 -translate-y-1/2"
>
<div
style={{
transform: transform,
}}
className="absolute left-1/2 p-4 top-1/2 flex justify-start items-start rounded-2xl shadow-[0_8px_16px_rgb(0_0_0/0.4)] bg-black border border-white/[0.1] group-hover/pin:border-white/[0.2] transition duration-700 overflow-hidden"
>
<div className={cn(" relative z-50 ", className)}>{children}</div>
</div>
</div>
<PinPerspective title={title} href={href} />
</a>
);
};
export const PinPerspective = ({
title,
href,
}: {
title?: string;
href?: string;
}) => {
return (
<motion.div className="pointer-events-none w-96 h-80 flex items-center justify-center opacity-0 group-hover/pin:opacity-100 z-[60] transition duration-500">
<div className=" w-full h-full -mt-7 flex-none inset-0">
<div className="absolute top-0 inset-x-0 flex justify-center">
<a
href={href}
target={"_blank"}
className="relative flex space-x-2 items-center z-10 rounded-full bg-zinc-950 py-0.5 px-4 ring-1 ring-white/10 "
>
<span className="relative z-20 text-white text-xs font-bold inline-block py-0.5">
{title}
</span>
<span className="absolute -bottom-0 left-[1.125rem] h-px w-[calc(100%-2.25rem)] bg-gradient-to-r from-emerald-400/0 via-emerald-400/90 to-emerald-400/0 transition-opacity duration-500 group-hover/btn:opacity-40"></span>
</a>
</div>
<div
style={{
perspective: "1000px",
transform: "rotateX(70deg) translateZ(0)",
}}
className="absolute left-1/2 top-1/2 ml-[0.09375rem] mt-4 -translate-x-1/2 -translate-y-1/2"
>
<>
<motion.div
initial={{
opacity: 0,
scale: 0,
x: "-50%",
y: "-50%",
}}
animate={{
opacity: [0, 1, 0.5, 0],
scale: 1,
z: 0,
}}
transition={{
duration: 6,
repeat: Infinity,
delay: 0,
}}
className="absolute left-1/2 top-1/2 h-[11.25rem] w-[11.25rem] rounded-[50%] bg-sky-500/[0.08] shadow-[0_8px_16px_rgb(0_0_0/0.4)]"
></motion.div>
<motion.div
initial={{
opacity: 0,
scale: 0,
x: "-50%",
y: "-50%",
}}
animate={{
opacity: [0, 1, 0.5, 0],
scale: 1,
z: 0,
}}
transition={{
duration: 6,
repeat: Infinity,
delay: 2,
}}
className="absolute left-1/2 top-1/2 h-[11.25rem] w-[11.25rem] rounded-[50%] bg-sky-500/[0.08] shadow-[0_8px_16px_rgb(0_0_0/0.4)]"
></motion.div>
<motion.div
initial={{
opacity: 0,
scale: 0,
x: "-50%",
y: "-50%",
}}
animate={{
opacity: [0, 1, 0.5, 0],
scale: 1,
z: 0,
}}
transition={{
duration: 6,
repeat: Infinity,
delay: 4,
}}
className="absolute left-1/2 top-1/2 h-[11.25rem] w-[11.25rem] rounded-[50%] bg-sky-500/[0.08] shadow-[0_8px_16px_rgb(0_0_0/0.4)]"
></motion.div>
</>
</div>
<>
<motion.div className="absolute right-1/2 bottom-1/2 bg-gradient-to-b from-transparent to-cyan-500 translate-y-[14px] w-px h-20 group-hover/pin:h-40 blur-[2px]" />
<motion.div className="absolute right-1/2 bottom-1/2 bg-gradient-to-b from-transparent to-cyan-500 translate-y-[14px] w-px h-20 group-hover/pin:h-40 " />
<motion.div className="absolute right-1/2 translate-x-[1.5px] bottom-1/2 bg-cyan-600 translate-y-[14px] w-[4px] h-[4px] rounded-full z-40 blur-[3px]" />
<motion.div className="absolute right-1/2 translate-x-[0.5px] bottom-1/2 bg-cyan-300 translate-y-[14px] w-[2px] h-[2px] rounded-full z-40 " />
</>
</div>
</motion.div>
);
};
Props
Prop name | Type | Description |
---|---|---|
className | string | The class name of the child component. |
containerClassName | string | The class name of the Container Component. |
title | string | ReactNode | Title that shows up on hover. |
href | string | Link of the component that you want to redirect to. |
children | ReactNode | The component that you want to render. |
Build websites faster and 10x better than your competitors with Aceternity UI Pro
With the best in class components and templates, stand out from the crowd and get more attention to your website. Trusted by founders and entrepreneurs from all over the world.
I've been working with Manu for a couple of months now and I can't express enough how impressed I am with his talent. Manu's JavaScript/React web UI programming skills are through the roof. He's he...
Tony Pujals
Founder at Fantastic Realms, Tech Lead at Google