Note: This component is written in javascript
Installation
pnpm add cobe
pnpm add cobe
Copy and paste the following code into your project.
components/eldoraui/cobeglobe.jsx
"use client";
import createGlobe from "cobe";
import { useEffect, useRef } from "react";
export function Cobe() {
const canvasRef = useRef();
useEffect(() => {
let phi = 0;
let width = 0;
const onResize = () =>
canvasRef.current && (width = canvasRef.current.offsetWidth);
window.addEventListener("resize", onResize);
onResize();
const globe = createGlobe(canvasRef.current, {
devicePixelRatio: 2,
width: width * 2,
height: width * 2,
phi: 0,
theta: 0.3,
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) => {
// Called on every animation frame.
// `state` will be an empty object, return updated params.
state.phi = phi;
phi += 0.005;
state.width = width * 2;
state.height = width * 2;
},
});
setTimeout(() => (canvasRef.current.style.opacity = "1"));
return () => {
globe.destroy();
window.removeEventListener("resize", onResize);
};
}, []);
return (
<div
style={{
width: "100%",
maxWidth: 600,
aspectRatio: 1,
margin: "auto",
position: "relative",
}}
>
<canvas
ref={canvasRef}
style={{
width: "100%",
height: "100%",
contain: "layout paint size",
opacity: 0,
transition: "opacity 1s ease",
}}
/>
</div>
);
}
Update the import paths to match your project setup.
Variants
Draggable
Copy and paste the following code into your project.
components/eldoraui/cobeglobedraggable.jsx
"use client";
import createGlobe from "cobe";
import { useEffect, useRef } from "react";
import { useSpring } from "react-spring";
export function CobeDraggable() {
const canvasRef = useRef();
const pointerInteracting = useRef(null);
const pointerInteractionMovement = useRef(0);
const [{ r }, api] = useSpring(() => ({
r: 0,
config: {
mass: 1,
tension: 280,
friction: 40,
precision: 0.001,
},
}));
useEffect(() => {
let width = 0;
const onResize = () =>
canvasRef.current && (width = canvasRef.current.offsetWidth);
window.addEventListener("resize", onResize);
onResize();
const globe = createGlobe(canvasRef.current, {
devicePixelRatio: 2,
width: width * 2,
height: width * 2,
phi: 0,
theta: 0.3,
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) => {
state.phi = r.get();
state.width = width * 2;
state.height = width * 2;
},
});
setTimeout(() => (canvasRef.current.style.opacity = "1"));
return () => {
globe.destroy();
window.removeEventListener("resize", onResize);
};
}, []);
return (
<div
style={{
width: "100%",
maxWidth: 600,
aspectRatio: 1,
margin: "auto",
position: "relative",
}}
>
<canvas
ref={canvasRef}
onPointerDown={(e) => {
pointerInteracting.current =
e.clientX - pointerInteractionMovement.current;
canvasRef.current.style.cursor = "grabbing";
}}
onPointerUp={() => {
pointerInteracting.current = null;
canvasRef.current.style.cursor = "grab";
}}
onPointerOut={() => {
pointerInteracting.current = null;
canvasRef.current.style.cursor = "grab";
}}
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>
);
}
Update the import paths to match your project setup.
Draggable & Auto Rotate
Copy and paste the following code into your project.
components/eldoraui/cobeglobeautodraggable.jsx
"use client";
import createGlobe from "cobe";
import { useEffect, useRef } from "react";
import { useSpring } from "react-spring";
export function CobeDraggableAuto() {
const canvasRef = useRef();
const pointerInteracting = useRef(null);
const pointerInteractionMovement = useRef(0);
const [{ r }, api] = useSpring(() => ({
r: 0,
config: {
mass: 1,
tension: 280,
friction: 40,
precision: 0.001,
},
}));
useEffect(() => {
let phi = 0;
let width = 0;
const onResize = () =>
canvasRef.current && (width = canvasRef.current.offsetWidth);
window.addEventListener("resize", onResize);
onResize();
const globe = createGlobe(canvasRef.current, {
devicePixelRatio: 2,
width: width * 2,
height: width * 2,
phi: 0,
theta: 0.3,
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) => {
// This prevents rotation while dragging
if (!pointerInteracting.current) {
// Called on every animation frame.
// `state` will be an empty object, return updated params.
phi += 0.005;
}
state.phi = phi + r.get();
state.width = width * 2;
state.height = width * 2;
},
});
setTimeout(() => (canvasRef.current.style.opacity = "1"));
return () => {
globe.destroy();
window.removeEventListener("resize", onResize);
};
}, []);
return (
<div
style={{
width: "100%",
maxWidth: 600,
aspectRatio: 1,
margin: "auto",
position: "relative",
}}
>
<canvas
ref={canvasRef}
onPointerDown={(e) => {
pointerInteracting.current =
e.clientX - pointerInteractionMovement.current;
canvasRef.current.style.cursor = "grabbing";
}}
onPointerUp={() => {
pointerInteracting.current = null;
canvasRef.current.style.cursor = "grab";
}}
onPointerOut={() => {
pointerInteracting.current = null;
canvasRef.current.style.cursor = "grab";
}}
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>
);
}
Update the import paths to match your project setup.
Rotate to Location
Copy and paste the following code into your project.
components/eldoraui/cobegloberotatetolocation.jsx
"use client";
import createGlobe from "cobe";
import { useEffect, useRef } from "react";
import { Button } from "@/components/ui/button";
export function CobeDragToLocation() {
const canvasRef = useRef();
const locationToAngles = (lat, long) => {
return [
Math.PI - ((long * Math.PI) / 180 - Math.PI / 2),
(lat * Math.PI) / 180,
];
};
const focusRef = useRef([0, 0]);
useEffect(() => {
let width = 0;
let currentPhi = 0;
let currentTheta = 0;
const doublePi = Math.PI * 2;
const onResize = () =>
canvasRef.current && (width = canvasRef.current.offsetWidth);
window.addEventListener("resize", onResize);
onResize();
const globe = createGlobe(canvasRef.current, {
devicePixelRatio: 2,
width: width * 2,
height: width * 2,
phi: 0,
theta: 0.3,
dark: 1,
diffuse: 3,
mapSamples: 16000,
mapBrightness: 1.2,
baseColor: [1, 1, 1],
markerColor: [251 / 255, 200 / 255, 21 / 255],
glowColor: [1.2, 1.2, 1.2],
markers: [
{ location: [37.78, -122.412], size: 0.1 },
{ location: [52.52, 13.405], size: 0.1 },
{ location: [35.676, 139.65], size: 0.1 },
{ location: [-34.6, -58.38], size: 0.1 },
],
onRender: (state) => {
state.phi = currentPhi;
state.theta = currentTheta;
const [focusPhi, focusTheta] = focusRef.current;
const distPositive = (focusPhi - currentPhi + doublePi) % doublePi;
const distNegative = (currentPhi - focusPhi + doublePi) % doublePi;
// Control the speed
if (distPositive < distNegative) {
currentPhi += distPositive * 0.08;
} else {
currentPhi -= distNegative * 0.08;
}
currentTheta = currentTheta * 0.92 + focusTheta * 0.08;
state.width = width * 2;
state.height = width * 2;
},
});
setTimeout(() => (canvasRef.current.style.opacity = "1"));
return () => {
globe.destroy();
window.removeEventListener("resize", onResize);
};
}, []);
return (
<div
style={{
width: "100%",
maxWidth: 600,
aspectRatio: 1,
margin: "auto",
position: "relative",
}}
>
<canvas
ref={canvasRef}
style={{
width: "100%",
height: "100%",
contain: "layout paint size",
opacity: 0,
transition: "opacity 1s ease",
}}
/>
<div
className="control-buttons flex flex-col items-center justify-center md:flex-row"
style={{ gap: ".5rem" }}
>
Rotate to:
<Button
onClick={() => {
focusRef.current = locationToAngles(37.78, -122.412);
}}
>
📍 San Francisco{" "}
</Button>
<Button
onClick={() => {
focusRef.current = locationToAngles(52.52, 13.405);
}}
>
📍 Berlin{" "}
</Button>
<Button
onClick={() => {
focusRef.current = locationToAngles(35.676, 139.65);
}}
>
📍 Tokyo{" "}
</Button>
<Button
onClick={() => {
focusRef.current = locationToAngles(-34.6, -58.38);
}}
>
📍 Buenos Aires{" "}
</Button>
</div>
</div>
);
}
<style jsx global>{`
.control-buttons button {
background: rgba(155, 155, 155, 0.2);
border-radius: 9px;
gap: 5px;
padding: 0.2rem 0.5rem;
cursor: pointer;
}
`}</style>;
Update the import paths to match your project setup.
Scaled
Copy and paste the following code into your project.
components/eldoraui/cobeglobescaled.jsx
"use client";
import createGlobe from "cobe";
import { useEffect, useRef } from "react";
export function CobeScaled() {
const canvasRef = useRef();
useEffect(() => {
let width = 0;
const onResize = () =>
canvasRef.current && (width = canvasRef.current.offsetWidth);
window.addEventListener("resize", onResize);
onResize();
const globe = createGlobe(canvasRef.current, {
devicePixelRatio: 2,
width: width * 2,
height: width * 2 * 0.4,
phi: 0,
theta: 0.3,
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: [],
scale: 2.5,
offset: [0, width * 2 * 0.4 * 0.6],
onRender: (state) => {
state.width = width * 2;
state.height = width * 2 * 0.4;
},
});
setTimeout(() => (canvasRef.current.style.opacity = "1"));
return () => {
globe.destroy();
window.removeEventListener("resize", onResize);
};
}, []);
return (
<div
style={{
width: "100%",
aspectRatio: 1 / 0.4,
margin: "auto",
position: "relative",
}}
>
<canvas
ref={canvasRef}
style={{
width: "100%",
height: "100%",
contain: "layout paint size",
opacity: 0,
transition: "opacity 1s ease",
}}
/>
</div>
);
}
Update the import paths to match your project setup.
props
Property | Description | Required |
---|---|---|
devicePixelRatio | The “DPR ”, defaults to 1 | |
width | The width of the canvas | Required |
height | The heigh t of the canvas | Required |
phi | The φ angle, 0 ≤ phi ≤ 2π | Required |
theta | The θ angle, -π ≤ theta ≤ π | Required |
dark | Display the globe in dark or light mode, 0 ≤ dark ≤ 1 | Required |
diffuse | Control the diffuse lighting, 0 ≤ diffuse | Required |
mapSamples | Number of dots displayed, 0 ≤ mapSamples ≤ 100000 | Required |
mapBrightness | Brightness of the dots, 0 ≤ mapBrightness | Required |
mapBaseBrightness | Brightness of samples that are not on the map, 0 ≤ mapBaseBrightness | |
baseColor | [r, g, b] of the base color, 0 ≤ r, g, b ≤ 1 | |
markerColor | [r, g, b] of the markers’ color, 0 ≤ r, g, b ≤ 1 | |
glowColor | [r, g, b] of the glow color, 0 ≤ r, g, b ≤ 1 | |
scale | Scales the globe, 0 ≤ scale | |
offset | [offsetX, offsetY] , offset of the globe in pixel | |
markers | An array of markers displayed | |
opacity | The transparency of the globe texture | |
onRender | A callback function called when a new frame is rendered | Required |