NRVM

Back to Discover
MotionAdvanced

Gravity Playground

Blocks that get pushed away by your mouse.

Live Preview
0
1
2
3
4
5
6
7
8
9
10
11

Move mouse to repel blocks

"use client";
import { useRef, useState, useEffect } from "react";
import { motion, useSpring, useMotionValue } from "framer-motion";

const NUM_CARDS = 12;

function GravityCard({ mousePos, index }: { mousePos: { x: number; y: number }, index: number }) {
  const ref = useRef<HTMLDivElement>(null);
  
  const x = useMotionValue(0);
  const y = useMotionValue(0);
  
  const springX = useSpring(x, { stiffness: 100, damping: 10 });
  const springY = useSpring(y, { stiffness: 100, damping: 10 });

  useEffect(() => {
    if (!ref.current) return;
    
    const rect = ref.current.getBoundingClientRect();
    const centerX = rect.left + rect.width / 2;
    const centerY = rect.top + rect.height / 2;
    
    const dx = centerX - mousePos.x;
    const dy = centerY - mousePos.y;
    const dist = Math.sqrt(dx * dx + dy * dy);
    
    if (dist < 150 && mousePos.x !== -9999) {
      const force = (150 - dist) / 150;
      x.set((dx / dist) * force * 50);
      y.set((dy / dist) * force * 50);
    } else {
      x.set(0);
      y.set(0);
    }
  }, [mousePos.x, mousePos.y, x, y]);

  return (
    <motion.div
      ref={ref}
      style={{ x: springX, y: springY }}
      className="w-16 h-16 sm:w-20 sm:h-20 bg-zinc-900 border border-zinc-800 rounded-xl shadow-lg flex items-center justify-center text-zinc-500 font-mono text-xs cursor-default select-none hover:bg-zinc-800 transition-colors"
    >
      {index}
    </motion.div>
  );
}

export function GravityPlayground() {
  const [mousePos, setMousePos] = useState({ x: -9999, y: -9999 });

  return (
    <div 
      className="flex flex-col items-center justify-center w-full h-[400px] bg-[#0a0a0a] rounded-xl border border-border relative overflow-hidden"
      onMouseMove={(e) => setMousePos({ x: e.clientX, y: e.clientY })}
      onMouseLeave={() => setMousePos({ x: -9999, y: -9999 })}
    >
      <div className="grid grid-cols-4 gap-4">
        {Array.from({ length: NUM_CARDS }).map((_, i) => (
          <GravityCard key={i} index={i} mousePos={mousePos} />
        ))}
      </div>
      <p className="text-sm text-zinc-500 absolute bottom-4 pointer-events-none">Move mouse to repel blocks</p>
    </div>
  );
}