-
Notifications
You must be signed in to change notification settings - Fork 205
Open
Description
import createGlobe from "cobe";
import { useEffect, useRef } from "react";
import { useSpring } from "react-spring";
const THETA = 0.3;
// m = [-sin(phi)*cos(theta), sin(theta), cos(phi)*cos(theta)]
function centerLatLon(phi: number, theta: number): [number, number] {
const c = Math.cos(theta);
const s = Math.sin(theta);
const sf = Math.sin(phi);
const cf = Math.cos(phi);
const mx = -sf * c;
const my = s;
const mz = cf * c;
const lat = Math.asin(my) * (180 / Math.PI);
const lon = Math.atan2(-mz, mx) * (180 / Math.PI);
return [lat, lon];
}
export function Cobe() {
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const pointerInteracting = useRef<number | null>(null);
const pointerInteractionMovement = useRef(0);
// Base rotation (committed after each drag)
const phiRef = useRef(0);
// Persistent markers across drags
const markersRef = useRef<{ location: [number, number]; size: number }[]>([]);
const [{ r }, api] = useSpring(() => ({
r: 0,
config: {
mass: 1,
tension: 280,
friction: 40,
precision: 0.001,
},
}));
useEffect(() => {
let width = 0;
const onResize = () => {
if (canvasRef.current) width = canvasRef.current.offsetWidth || 0;
};
window.addEventListener("resize", onResize);
onResize();
const globe = createGlobe(canvasRef.current as HTMLCanvasElement, {
devicePixelRatio: 2,
width: width * 2,
height: width * 2,
phi: 0,
theta: THETA,
dark: 1,
diffuse: 3,
mapSamples: 16000,
mapBrightness: 1.2,
baseColor: [1, 1, 1],
markerColor: [251 / 255, 100 / 255, 21 / 255],
glowColor: [1.2, 1.2, 1.2],
markers: [],
onRender: (state) => {
// Only rotate based on current drag offset + committed base
const currentPhi = phiRef.current + r.get();
state.phi = currentPhi;
state.theta = THETA;
state.width = width * 2;
state.height = width * 2;
// Persist existing pins only
state.markers = markersRef.current;
},
});
setTimeout(() => {
if (canvasRef.current) canvasRef.current.style.opacity = "1";
});
return () => {
globe.destroy();
window.removeEventListener("resize", onResize);
};
}, [r]);
const finalizeDragAndPin = () => {
// Only finalize if we were dragging
if (pointerInteracting.current !== null) {
const currentPhi = phiRef.current + r.get();
const [lat, lon] = centerLatLon(currentPhi, THETA);
// Add a persistent pin at the current center
markersRef.current = [
...markersRef.current,
{ location: [lat, lon], size: 0.06 },
];
// Commit the rotation and reset drag delta
phiRef.current = currentPhi;
pointerInteracting.current = null;
pointerInteractionMovement.current = 0;
api.start({ r: 0 });
if (canvasRef.current) canvasRef.current.style.cursor = "grab";
}
};
return (
<div
style={{
width: "100%",
maxWidth: 600,
aspectRatio: 1,
margin: "auto",
position: "relative",
}}
>
<canvas
ref={canvasRef}
onPointerDown={(e) => {
pointerInteracting.current =
e.clientX - pointerInteractionMovement.current;
if (canvasRef.current) canvasRef.current.style.cursor = "grabbing";
}}
onPointerUp={finalizeDragAndPin}
onPointerOut={finalizeDragAndPin}
onTouchEnd={finalizeDragAndPin}
onMouseMove={(e) => {
if (pointerInteracting.current !== null) {
const delta = e.clientX - pointerInteracting.current;
pointerInteractionMovement.current = delta;
api.start({ r: delta / 200 });
}
}}
onTouchMove={(e) => {
if (pointerInteracting.current !== null && e.touches[0]) {
const delta = e.touches[0].clientX - pointerInteracting.current;
pointerInteractionMovement.current = delta;
api.start({ r: delta / 100 });
}
}}
style={{
width: "100%",
height: "100%",
cursor: "grab",
contain: "layout paint size",
opacity: 0,
transition: "opacity 1s ease",
}}
/>
</div>
);
}It is required to update twice before the last pin point can be added:
https://github.com/user-attachments/assets/a9c08048-13c1-42f0-b492-f2ea0ee35471
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels