NRVM

Back to Discover
MotionAdvanced

Physics Dock

An Apple OS-style floating dock where icons smoothly magnify based on mouse proximity.

Live Preview
"use client";

import { motion, useMotionValue, useTransform, useSpring } from "framer-motion";
import { useRef } from "react";
import { Home, Search, Bell, Settings, User } from "lucide-react";

export function PhysicsDock() {
  const mouseX = useMotionValue(Infinity);

  return (
    <div className="flex items-center justify-center w-full h-40 bg-[#0a0a0a] rounded-xl border border-border">
      <motion.div
        onMouseMove={(e) => mouseX.set(e.pageX)}
        onMouseLeave={() => mouseX.set(Infinity)}
        className="flex items-end gap-4 rounded-2xl bg-[#111] px-4 pb-3 pt-4 border border-border shadow-xl"
      >
        {[
          { icon: Home, label: "Home" },
          { icon: Search, label: "Search" },
          { icon: Bell, label: "Notifications" },
          { icon: Settings, label: "Settings" },
          { icon: User, label: "Profile" },
        ].map((item, i) => (
          <DockIcon key={i} mouseX={mouseX}>
            <item.icon className="h-6 w-6 text-foreground" />
          </DockIcon>
        ))}
      </motion.div>
    </div>
  );
}

function DockIcon({ mouseX, children }: { mouseX: any; children: React.ReactNode }) {
  const ref = useRef<HTMLDivElement>(null);

  const distance = useTransform(mouseX, (val: number) => {
    const bounds = ref.current?.getBoundingClientRect() ?? { x: 0, width: 0 };
    return val - bounds.x - bounds.width / 2;
  });

  const widthSync = useTransform(distance, [-150, 0, 150], [40, 80, 40]);
  const width = useSpring(widthSync, { mass: 0.1, stiffness: 150, damping: 12 });

  return (
    <motion.div
      ref={ref}
      style={{ width, height: width }}
      className="flex items-center justify-center rounded-full bg-zinc-800 border border-zinc-700 shadow-md cursor-pointer hover:bg-zinc-700 transition-colors"
    >
      {children}
    </motion.div>
  );
}