Magnified Dock

Recreation of the Mac OS dock with magnification.

View at 640px with a mouse
to see the interaction.

Recreation of the magnification effect from the Mac OS dock.

Uses Framer Motion to track the mouse position in a MotionValue called mouseX. Each icon then derives the pixel distance from the mouse position to its center, and uses a transform to adjust its width based on that distance.

Infinity is used as the null value for mouseX to keep the transforms clean – no active/inactive state is needed to enable magnification since Infinity always falls outside the domain and thus resets the width of each icon.

Note that this demo is not identical to the actual Mac OS dock, since the width of the dock on the far side of the mouse doesn't move with the icons. This behavior is left as an exercise for the reader – or for a future recipe!

Libraries used

Code

import {
  MotionValue,
  motion,
  useMotionValue,
  useSpring,
  useTransform,
} from "framer-motion";
import { useRef } from "react";

function Dock() {
  let mouseX = useMotionValue(Infinity);

  return (
    <motion.div
      onMouseMove={(e) => mouseX.set(e.pageX)}
      onMouseLeave={() => mouseX.set(Infinity)}
      className="mx-auto flex h-16 items-end gap-4 rounded-2xl bg-gray-700 px-4 pb-3"
    >
      {[...Array(8).keys()].map((i) => (
        <AppIcon mouseX={mouseX} key={i} />
      ))}
    </motion.div>
  );
}

function AppIcon({ mouseX }: { mouseX: MotionValue }) {
  let ref = useRef<HTMLDivElement>(null);

  let distance = useTransform(mouseX, (val) => {
    let bounds = ref.current?.getBoundingClientRect() ?? { x: 0, width: 0 };

    return val - bounds.x - bounds.width / 2;
  });

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

  return (
    <motion.div
      ref={ref}
      style={{ width }}
      className="aspect-square w-10 rounded-full bg-gray-400"
    />
  );
}