Docs
Text Hover Effect
A text hover effect that animates and outlines gradient on hover
Installation
Install dependencies
npm install framer-motion lucide-react
Update tailwind.config.js
Add the following to your tailwind.config.js file.
module.exports = {
theme: {
extend: {
}
}
}
Run the following command
It will create a new file text-hover-effect.tsx
inside the components/mage-ui/text
directory.
mkdir -p components/mage-ui/text && touch components/mage-ui/text/text-hover-effect.tsx
Paste the code
Open the newly created file and paste the following code:
"use client";
import React, { useRef, useEffect, useState } from "react";
import { motion } from "framer-motion";
const TextHoverEffect: React.FC<{ text: string; duration?: number }> = ({
text,
duration,
}) => {
const svgRef = useRef<SVGSVGElement>(null);
const [cursor, setCursor] = useState({ x: 0, y: 0 });
const [hovered, setHovered] = useState(false);
const [maskPosition, setMaskPosition] = useState({ cx: "50%", cy: "50%" });
useEffect(() => {
if (svgRef.current && cursor.x !== null && cursor.y !== null) {
const svgRect = svgRef.current.getBoundingClientRect();
const cxPercentage = ((cursor.x - svgRect.left) / svgRect.width) * 100;
const cyPercentage = ((cursor.y - svgRect.top) / svgRect.height) * 100;
setMaskPosition({
cx: `${cxPercentage}%`,
cy: `${cyPercentage}%`,
});
}
}, [cursor]);
return (
<svg
ref={svgRef}
width="100%"
height="100%"
viewBox="0 0 300 100"
xmlns="http://www.w3.org/2000/svg"
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
onMouseMove={(e) => setCursor({ x: e.clientX, y: e.clientY })}
className="select-none"
>
<defs>
<linearGradient id="textGradient" gradientUnits="userSpaceOnUse" cx="50%" cy="50%" r="25%">
{hovered && (
<>
<stop offset="0%" stopColor="#eab308" />
<stop offset="25%" stopColor="#ef4444" />
<stop offset="50%" stopColor="#3b82f6" />
<stop offset="75%" stopColor="#06b6d4" />
<stop offset="100%" stopColor="#8b5cf6" />
</>
)}
</linearGradient>
<motion.radialGradient
id="revealMask"
gradientUnits="userSpaceOnUse"
r="20%"
initial={{ cx: "50%", cy: "50%" }}
animate={maskPosition}
transition={{ duration: duration ?? 0, ease: "easeOut" }}
>
<stop offset="0%" stopColor="white" />
<stop offset="100%" stopColor="black" />
</motion.radialGradient>
<mask id="textMask">
<rect x="0" y="0" width="100%" height="100%" fill="url(#revealMask)" />
</mask>
</defs>
<text
x="50%"
y="50%"
textAnchor="middle"
dominantBaseline="middle"
strokeWidth="0.3"
className="fill-transparent stroke-neutral-200 font-[helvetica] text-7xl font-bold dark:stroke-neutral-800"
style={{ opacity: hovered ? 0.7 : 0 }}
>
{text}
</text>
<motion.text
x="50%"
y="50%"
textAnchor="middle"
dominantBaseline="middle"
strokeWidth="0.3"
className="fill-transparent stroke-neutral-200 font-[helvetica] text-7xl font-bold dark:stroke-neutral-800"
initial={{ strokeDashoffset: 1000, strokeDasharray: 1000 }}
animate={{ strokeDashoffset: 0, strokeDasharray: 1000 }}
transition={{ duration: 4, ease: "easeInOut" }}
>
{text}
</motion.text>
<text
x="50%"
y="50%"
textAnchor="middle"
dominantBaseline="middle"
stroke="url(#textGradient)"
strokeWidth="0.3"
mask="url(#textMask)"
className="fill-transparent font-[helvetica] text-7xl font-bold"
>
{text}
</text>
</svg>
);
};
const TextHoverEffectDemo: React.FC = () => {
return (
<div className="h-[40rem] flex items-center justify-center bg-black">
<TextHoverEffect text="MAGE" />
</div>
);
};
export default TextHoverEffectDemo;