Direction Aware Hover
A direction aware hover effect using Framer Motion, Tailwindcss and good old javascript.
Open in 
In the mountains
$1299 / night
Installation
Install dependencies
npm i motion clsx tailwind-merge
Add util file
lib/utils.ts
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/direction-aware-hover.tsx
"use client";
import { useRef, useState } from "react";
import Image from "next/image";
import { AnimatePresence, motion } from "framer-motion";
import { cn } from "@/lib/utils";
export const DirectionAwareHover = ({
imageUrl,
children,
childrenClassName,
imageClassName,
className,
}: {
imageUrl: string;
children: React.ReactNode | string;
childrenClassName?: string;
imageClassName?: string;
className?: string;
}) => {
const ref = useRef<HTMLDivElement>(null);
const [direction, setDirection] = useState<
"top" | "bottom" | "left" | "right" | string
>("left");
const handleMouseEnter = (
event: React.MouseEvent<HTMLDivElement, MouseEvent>
) => {
if (!ref.current) return;
const direction = getDirection(event, ref.current);
console.log("direction", direction);
switch (direction) {
case 0:
setDirection("top");
break;
case 1:
setDirection("right");
break;
case 2:
setDirection("bottom");
break;
case 3:
setDirection("left");
break;
default:
setDirection("left");
break;
}
};
const getDirection = (
ev: React.MouseEvent<HTMLDivElement, MouseEvent>,
obj: HTMLElement
) => {
const { width: w, height: h, left, top } = obj.getBoundingClientRect();
const x = ev.clientX - left - (w / 2) * (w > h ? h / w : 1);
const y = ev.clientY - top - (h / 2) * (h > w ? w / h : 1);
const d = Math.round(Math.atan2(y, x) / 1.57079633 + 5) % 4;
return d;
};
return (
<motion.div
onMouseEnter={handleMouseEnter}
ref={ref}
className={cn(
"md:h-96 w-60 h-60 md:w-96 bg-transparent rounded-lg overflow-hidden group/card relative",
className
)}
>
<AnimatePresence mode="wait">
<motion.div
className="relative h-full w-full"
initial="initial"
whileHover={direction}
exit="exit"
>
<motion.div className="group-hover/card:block hidden absolute inset-0 w-full h-full bg-black/40 z-10 transition duration-500" />
<motion.div
variants={variants}
className="h-full w-full relative bg-gray-50 dark:bg-black"
transition={{
duration: 0.2,
ease: "easeOut",
}}
>
<Image
alt="image"
className={cn(
"h-full w-full object-cover scale-[1.15]",
imageClassName
)}
width="1000"
height="1000"
src={imageUrl}
/>
</motion.div>
<motion.div
variants={textVariants}
transition={{
duration: 0.5,
ease: "easeOut",
}}
className={cn(
"text-white absolute bottom-4 left-4 z-40",
childrenClassName
)}
>
{children}
</motion.div>
</motion.div>
</AnimatePresence>
</motion.div>
);
};
const variants = {
initial: {
x: 0,
},
exit: {
x: 0,
y: 0,
},
top: {
y: 20,
},
bottom: {
y: -20,
},
left: {
x: 20,
},
right: {
x: -20,
},
};
const textVariants = {
initial: {
y: 0,
x: 0,
opacity: 0,
},
exit: {
y: 0,
x: 0,
opacity: 0,
},
top: {
y: -20,
opacity: 1,
},
bottom: {
y: 2,
opacity: 1,
},
left: {
x: -2,
opacity: 1,
},
right: {
x: 20,
opacity: 1,
},
};
Props
Prop | Type | Description |
---|---|---|
imageUrl | string | The URL of the image to be displayed. |
children | React.ReactNode or string | The content to be displayed over the image. |
childrenClassName | string (optional) | The CSS class to be applied to the children. |
imageClassName | string (optional) | The CSS class to be applied to the image. |
Get beautiful, hand-crafted templates and components with Aceternity UI Pro
Professional, beautiful and elegant templates for your business. Get the best component packs and templates with Aceternity UI Pro.
The work that Manu did laid the foundation of online education that we provide today. The website he built for us is used by thousands of students every day. He took the requirements and built the ...
Jagvinder Kour
Chairperson at Golden Bells Academy