Created
November 22, 2025 16:04
-
-
Save divv919/075b3707ea2b48e358af21a1d79abfa2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| "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