ComponentsText Hover Enter
Text Hover Enter
Hover Me
Hover Me
Terminal
npm i framer-motion clsx tailwind-merge
utils/cn.ts
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
TextHoverEnter.tsx
"use client"; // @NOTE: add in case you are using Next.js
import { cn } from "@/utils/cn";
import { motion } from "framer-motion";
export function TextHoverEnterExample() {
return <TextHoverEnter>Hover Me</TextHoverEnter>;
}
const DURATION = 0.25;
const STAGGER = 0.025;
type TextGlitchProps = React.ComponentProps<"div">;
function TextHoverEnter({ children, className }: TextGlitchProps) {
if (typeof children !== "string") {
return null;
}
const letters = children
.split("")
.map((letter) => (letter === " " ? "\u00A0" : letter));
return (
<motion.div
className={cn(
"relative block overflow-hidden whitespace-nowrap text-base font-medium text-neutral-400",
className,
)}
initial="initial"
whileHover="hovered"
style={{ lineHeight: 0.9 }}
>
<div>
{letters.map((letter, i) => (
<motion.span
key={i}
className="inline-block"
variants={{
initial: { y: 0 },
hovered: { y: "-100%" },
}}
transition={{
duration: DURATION,
ease: "easeInOut",
delay: STAGGER * i,
}}
>
{letter}
</motion.span>
))}
</div>
<div className="absolute inset-0">
{letters.map((letter, i) => (
<motion.span
key={i}
className="inline-block"
variants={{
initial: { y: "100%" },
hovered: { y: 0 },
}}
transition={{
duration: DURATION,
ease: "easeInOut",
delay: STAGGER * i,
}}
>
{letter}
</motion.span>
))}
</div>
</motion.div>
);
}