Docs
Hover Image Links
Hover Image Links are interactive links that change or reveal an image when hovered over, enhancing user engagement.
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 hover-image-links.tsx
inside the components/mage-ui/links
directory.
mkdir -p components/mage-ui/links && touch components/mage-ui/links/hover-image-links.tsx
Paste the code
Open the newly created file and paste the following code:
"use client";
import { useMotionValue, motion, useSpring, useTransform } from "framer-motion";
import React, { useRef } from "react";
import { FiArrowRight } from "react-icons/fi";
export const HoverImageLinks = () => {
return (
<section className="bg-neutral-950 p-4 md:p-8 h-full w-[90vw]">
<div className="mx-auto max-w-5xl">
<Link
heading="About"
subheading="Learn what we do here"
imgSrc="https://images.unsplash.com/photo-1635373670332-43ea883bb081?q=80&w=2781&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
href="#"
/>
<Link
heading="Clients"
subheading="We work with great people"
imgSrc="https://images.unsplash.com/photo-1576174464184-fb78fe882bfd?q=80&w=2787&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
href="#"
/>
<Link
heading="Portfolio"
subheading="Our work speaks for itself"
imgSrc="https://images.unsplash.com/photo-1503751071777-d2918b21bbd9?q=80&w=2670&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
href="#"
/>
<Link
heading="Careers"
subheading="We want cool people"
imgSrc="https://images.unsplash.com/photo-1620428268482-cf1851a36764?q=80&w=2609&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
href="#"
/>
<Link
heading="Fun"
subheading="Incase you're bored"
imgSrc="https://images.unsplash.com/photo-1602212096437-d0af1ce0553e?q=80&w=2671&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
href="#"
/>
</div>
</section>
);
};
interface LinkProps {
heading: string;
imgSrc: string;
subheading: string;
href: string;
}
const Link = ({ heading, imgSrc, subheading, href }: LinkProps) => {
const ref = useRef<HTMLAnchorElement | null>(null);
const x = useMotionValue(0);
const y = useMotionValue(0);
const mouseXSpring = useSpring(x);
const mouseYSpring = useSpring(y);
const top = useTransform(mouseYSpring, [0.5, -0.5], ["40%", "60%"]);
const left = useTransform(mouseXSpring, [0.5, -0.5], ["60%", "70%"]);
const handleMouseMove = (
e: React.MouseEvent<HTMLAnchorElement, MouseEvent>
) => {
const rect = ref.current!.getBoundingClientRect();
const width = rect.width;
const height = rect.height;
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
const xPct = mouseX / width - 0.5;
const yPct = mouseY / height - 0.5;
x.set(xPct);
y.set(yPct);
};
return (
<motion.a
href={href}
ref={ref}
onMouseMove={handleMouseMove}
initial="initial"
whileHover="whileHover"
className="group relative flex items-center justify-between border-b-2 border-neutral-700 py-4 transition-colors duration-500 hover:border-neutral-50 md:py-8"
>
<div>
<motion.span
variants={{
initial: { x: 0 },
whileHover: { x: -16 },
}}
transition={{
type: "spring",
staggerChildren: 0.075,
delayChildren: 0.25,
}}
className="relative z-10 block text-4xl font-bold text-neutral-500 transition-colors duration-500 group-hover:text-neutral-50 md:text-6xl"
>
{heading.split("").map((l, i) => (
<motion.span
variants={{
initial: { x: 0 },
whileHover: { x: 16 },
}}
transition={{ type: "spring" }}
className="inline-block"
key={i}
>
{l}
</motion.span>
))}
</motion.span>
<span className="relative z-10 mt-2 block text-base text-neutral-500 transition-colors duration-500 group-hover:text-neutral-50">
{subheading}
</span>
</div>
<motion.img
style={{
top,
left,
translateX: "-50%",
translateY: "-50%",
}}
variants={{
initial: { scale: 0, rotate: "-12.5deg" },
whileHover: { scale: 1, rotate: "12.5deg" },
}}
transition={{ type: "spring" }}
src={imgSrc}
className="absolute z-0 h-24 w-32 rounded-lg object-cover md:h-48 md:w-64"
alt={`Image representing a link for ${heading}`}
/>
<motion.div
variants={{
initial: {
x: "25%",
opacity: 0,
},
whileHover: {
x: "0%",
opacity: 1,
},
}}
transition={{ type: "spring" }}
className="relative z-10 p-4"
>
<FiArrowRight className="text-5xl text-neutral-50" />
</motion.div>
</motion.a>
);
};