← All elements

Liquid Metaballs

Organic

Four chrome spheres orbit and overlap to simulate merging liquid metaballs.

Usage
Install: three @react-three/fiber @react-three/drei
Drop <LiquidMetaballs /> inside your own <Canvas>.
Mood
Fluid, mercurial, alive.
Colors
Chrome cyan spheres with violet and magenta reflections on black.
Motion
Four spheres orbit at different speeds and phases, overlapping like mercury drops.
View component source
import { useRef } from 'react';
import { useFrame } from '@react-three/fiber';
import { MeshDistortMaterial } from '@react-three/drei';
import type { Group } from 'three';

// Safe fallback: 4 orbiting distorted spheres that overlap to simulate metaball merging.
// MarchingCubes from drei crashes in the test-renderer environment.
const ORBIT_CONFIG = [
  { radius: 0.7, speed: 1.0, phase: 0, y: 0.2, size: 0.55 },
  { radius: 0.55, speed: -1.3, phase: Math.PI / 2, y: -0.15, size: 0.5 },
  { radius: 0.8, speed: 0.8, phase: Math.PI, y: 0.05, size: 0.45 },
  { radius: 0.45, speed: 1.6, phase: (3 * Math.PI) / 2, y: 0.3, size: 0.42 },
];

export default function LiquidMetaballs({ color = '#22e0ff', scale = 1 }: { color?: string; scale?: number }) {
  const groupRef = useRef<Group>(null);

  useFrame((state) => {
    const group = groupRef.current;
    if (!group) return;
    const t = state.clock.elapsedTime;
    ORBIT_CONFIG.forEach((cfg, i) => {
      const child = group.children[i];
      if (!child) return;
      const angle = t * cfg.speed + cfg.phase;
      child.position.x = Math.cos(angle) * cfg.radius;
      child.position.z = Math.sin(angle) * cfg.radius;
      child.position.y = cfg.y + Math.sin(t * 0.7 + cfg.phase) * 0.15;
    });
  });

  return (
    <group ref={groupRef} scale={scale}>
      {ORBIT_CONFIG.map((cfg, i) => (
        <mesh key={i}>
          <sphereGeometry args={[cfg.size, 32, 32]} />
          <MeshDistortMaterial
            color={color}
            metalness={1}
            roughness={0.05}
            distort={0.3}
            speed={2}
            emissive={color}
            emissiveIntensity={0.2}
          />
        </mesh>
      ))}
    </group>
  );
}