Interactive Globe
Preview
Code
"use client";
import { Canvas, useFrame } from "@react-three/fiber";
import { OrbitControls, Sphere, Points, PointMaterial, Tube } from "@react-three/drei";
import { useRef, useMemo, type FC } from "react";
import * as THREE from "three";
interface InteractiveGlobeProps {
pointColor?: string;
arcColor?: string;
globeRadius?: number;
pointsCount?: number;
arcsCount?: number;
arcMaxAltitude?: number;
autoRotateSpeed?: number;
}
const Globe: FC<InteractiveGlobeProps> = ({
pointColor = "#6366f1", // indigo-500
arcColor = "#a855f7", // purple-500
globeRadius = 2.5,
pointsCount = 5000,
arcsCount = 10,
arcMaxAltitude = 1.5,
autoRotateSpeed = 0.2,
}) => {
const globeRef = useRef<THREE.Group>(null);
useFrame((_, delta) => {
if (globeRef.current) {
globeRef.current.rotation.y += delta * autoRotateSpeed;
}
});
const points = useMemo(() => {
const p = new Array(pointsCount).fill(0).map(() => {
const phi = Math.acos(-1 + 2 * Math.random());
const theta = Math.sqrt(pointsCount * Math.PI) * phi;
const x = globeRadius * Math.cos(theta) * Math.sin(phi);
const y = globeRadius * Math.sin(theta) * Math.sin(phi);
const z = globeRadius * Math.cos(phi);
return new THREE.Vector3(x, y, z);
});
return new THREE.BufferGeometry().setFromPoints(p);
}, [pointsCount, globeRadius]);
const arcs = useMemo(() => {
const a = [];
for (let i = 0; i < arcsCount; i++) {
const start = new THREE.Vector3(
(Math.random() - 0.5) * 2 * globeRadius,
(Math.random() - 0.5) * 2 * globeRadius,
(Math.random() - 0.5) * 2 * globeRadius
).normalize().multiplyScalar(globeRadius);
const end = new THREE.Vector3(
(Math.random() - 0.5) * 2 * globeRadius,
(Math.random() - 0.5) * 2 * globeRadius,
(Math.random() - 0.5) * 2 * globeRadius
).normalize().multiplyScalar(globeRadius);
const altitude = Math.random() * arcMaxAltitude + 0.2;
const mid = start.clone().lerp(end, 0.5).normalize().multiplyScalar(globeRadius + altitude);
const curve = new THREE.QuadraticBezierCurve3(start, mid, end);
a.push(curve);
}
return a;
}, [arcsCount, globeRadius, arcMaxAltitude]);
return (
<group ref={globeRef}>
<Sphere args={[globeRadius, 64, 64]}>
<meshStandardMaterial color="#111827" roughness={0.9} metalness={0.1} />
</Sphere>
<Points geometry={points}>
<PointMaterial transparent color={pointColor} size={0.015} sizeAttenuation={true} depthWrite={false} />
</Points>
{arcs.map((curve, i) => (
<Tube key={i} args={[curve, 64, 0.01, 8, false]}>
<meshBasicMaterial color={arcColor} toneMapped={false} />
</Tube>
))}
</group>
);
};
const InteractiveGlobe: FC<InteractiveGlobeProps> = (props) => {
return (
<Canvas camera={{ position: [0, 0, 7], fov: 45 }}>
<ambientLight color="#ffffff" intensity={0.2} />
<directionalLight color="#ffffff" intensity={1} position={[2, 2, 2]} />
<Globe {...props} />
<OrbitControls enableZoom={false} enablePan={false} autoRotate={false} />
</Canvas>
);
};
export default InteractiveGlobe;