Skip to content

Instantly share code, notes, and snippets.

@divv919
Created November 22, 2025 16:04
Show Gist options
  • Select an option

  • Save divv919/075b3707ea2b48e358af21a1d79abfa2 to your computer and use it in GitHub Desktop.

Select an option

Save divv919/075b3707ea2b48e358af21a1d79abfa2 to your computer and use it in GitHub Desktop.
"use client";
import {
motion,
MotionValue,
useMotionValue,
useSpring,
useTransform,
useMotionValueEvent,
} from "motion/react";
import { useEffect, useRef, useState } from "react";
const cardsArray = Array.from({ length: 8 });
export default function DockEffect() {
const dockRef = useRef<HTMLDivElement | null>(null);
const x = useMotionValue(0);
const y = useMotionValue(0);
const [isDockHovered, setIsDockHovered] = useState(false);
useEffect(() => {
if (typeof window === "undefined") {
return;
}
const handleMouseMove = (e: MouseEvent) => {
x.set(e.clientX);
y.set(e.clientY);
};
window.addEventListener("mousemove", handleMouseMove);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
};
}, [x, y]);
return (
<>
<div
ref={dockRef}
className="absolute h-[48px] w-fit flex items-center gap-1 p-4 bg-blue-400"
onMouseEnter={() => setIsDockHovered(true)}
onMouseLeave={() => setIsDockHovered(false)}
>
{cardsArray.map((_, index) => {
return (
<CardItem
key={index}
index={index}
mousePosition={{ x, y }}
isDockHovered={isDockHovered}
dockRef={dockRef}
/>
);
})}
</div>
</>
);
}
function CardItem({
index,
mousePosition,
isDockHovered,
dockRef,
}: {
index: number;
mousePosition: { x: MotionValue<number>; y: MotionValue<number> };
isDockHovered: boolean;
dockRef: React.RefObject<HTMLDivElement | null>;
}) {
const [cardCenter, setCardCenter] = useState<number | null>(null);
const cardRef = useRef<HTMLDivElement | null>(null);
const baseSize = 48;
const spring = useSpring(baseSize, {
damping: 10,
stiffness: 150,
mass: 0.01,
});
// set center value
useEffect(() => {
if (!cardRef.current) {
return;
}
const rect = cardRef.current.getBoundingClientRect();
setCardCenter(rect.x + rect.width / 2);
}, []);
const dimension = useTransform(mousePosition.x, (mouseX) => {
if (!cardCenter || !dockRef.current) {
return baseSize;
}
const dockWidth = dockRef.current.getBoundingClientRect().width;
const distance = (mouseX - cardCenter) / dockWidth;
console.log({ distance });
return baseSize + 36 * Math.cos((distance * Math.PI) / 2) ** 12;
});
// Use useMotionValueEvent instead of manual listener
useMotionValueEvent(dimension, "change", (value) => {
if (isDockHovered) {
spring.set(value);
} else {
spring.set(baseSize);
}
});
useEffect(() => {
if (typeof window === "undefined") {
return;
}
const handleResize = () => {
if (!cardRef.current) {
return;
}
const rect = cardRef.current.getBoundingClientRect();
setCardCenter(rect.x + rect.width / 2);
};
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return (
<motion.div
className="bg-red-500 flex items-center justify-center"
ref={cardRef}
style={{
width: spring,
height: spring,
}}
>
{index}
</motion.div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment