Back to Component Explorer

3D Spatial Globe

effects

Expanded high-definition rotating 3D globe with interactive multi-axis dragging, latitude matrix, and beacons.

#globe#3d#interactive#spatial
Live Interactive Preview
360° Multi-Axis Spatial Rotation(20°, 45°)

Component Source Code

Select language in the dropdown
ThreeDGlobe.tsx
1'use client';
2
3import React, { useState, useEffect, useRef } from 'react';
4
5export default function ThreeDGlobe() {
6 const [rotation, setRotation] = useState({ x: 15, y: 45 });
7 const [isDragging, setIsDragging] = useState(false);
8 const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
9 const containerRef = useRef<HTMLDivElement>(null);
10
11 useEffect(() => {
12 if (isDragging) return;
13 const interval = setInterval(() => {
14 setRotation((prev) => ({ ...prev, y: (prev.y + 0.4) % 360 }));
15 }, 25);
16 return () => clearInterval(interval);
17 }, [isDragging]);
18
19 const handleMouseDown = (e: React.MouseEvent) => {
20 setIsDragging(true);
21 setDragStart({ x: e.clientX, y: e.clientY });
22 };
23
24 const handleMouseMove = (e: React.MouseEvent) => {
25 if (!isDragging) return;
26 const deltaX = e.clientX - dragStart.x;
27 const deltaY = e.clientY - dragStart.y;
28 setRotation((prev) => ({
29 x: Math.max(-75, Math.min(75, prev.x - deltaY * 0.3)),
30 y: (prev.y + deltaX * 0.3) % 360
31 }));
32 setDragStart({ x: e.clientX, y: e.clientY });
33 };
34
35 const handleMouseUp = () => setIsDragging(false);
36
37 const handleTouchStart = (e: React.TouchEvent) => {
38 if (e.touches.length === 1) {
39 setIsDragging(true);
40 setDragStart({ x: e.touches[0].clientX, y: e.touches[0].clientY });
41 }
42 };
43
44 const handleTouchMove = (e: React.TouchEvent) => {
45 if (!isDragging || e.touches.length !== 1) return;
46 const deltaX = e.touches[0].clientX - dragStart.x;
47 const deltaY = e.touches[0].clientY - dragStart.y;
48 setRotation((prev) => ({
49 x: Math.max(-75, Math.min(75, prev.x - deltaY * 0.3)),
50 y: (prev.y + deltaX * 0.3) % 360
51 }));
52 setDragStart({ x: e.touches[0].clientX, y: e.touches[0].clientY });
53 };
54
55 const meridians = [0, 30, 60, 90, 120, 150];
56 const parallels = [-60, -40, -20, 0, 20, 40, 60];
57
58 return (
59 <div
60 ref={containerRef}
61 onMouseDown={handleMouseDown}
62 onMouseMove={handleMouseMove}
63 onMouseUp={handleMouseUp}
64 onMouseLeave={handleMouseUp}
65 onTouchStart={handleTouchStart}
66 onTouchMove={handleTouchMove}
67 onTouchEnd={handleMouseUp}
68 className="relative flex flex-col items-center justify-center p-4 sm:p-6 cursor-grab active:cursor-grabbing select-none w-full"
69 >
70 <div className="relative h-56 w-56 sm:h-72 sm:w-72 md:h-80 md:w-80 flex items-center justify-center [perspective:1200px]">
71 <div className="absolute inset-0 rounded-full bg-blue-500/20 blur-3xl pointer-events-none" />
72 <div className="absolute inset-2 rounded-full border-2 border-cyan-500/40 bg-radial from-cyan-950/40 via-slate-950 to-slate-950 shadow-[0_0_50px_rgba(6,182,212,0.25)] pointer-events-none" />
73
74 <div
75 style={{
76 transform: `rotateX(${rotation.x}deg) rotateY(${rotation.y}deg)`,
77 transformStyle: 'preserve-3d',
78 transition: isDragging ? 'none' : 'transform 0.05s linear'
79 }}
80 className="relative h-48 w-48 sm:h-60 sm:w-60 md:h-64 md:w-64 flex items-center justify-center"
81 >
82 {meridians.map((deg) => (
83 <div
84 key={`meridian-${deg}`}
85 style={{
86 transform: `rotateY(${deg}deg)`,
87 transformStyle: 'preserve-3d'
88 }}
89 className="absolute inset-0 rounded-full border border-cyan-400/60 [backface-visibility:visible]"
90 />
91 ))}
92
93 {parallels.map((lat) => {
94 const scale = Math.cos((lat * Math.PI) / 180);
95 const translateY = -Math.sin((lat * Math.PI) / 180) * 110;
96 return (
97 <div
98 key={`parallel-${lat}`}
99 style={{
100 transform: `translateY(${translateY}px) rotateX(90deg) scale(${scale})`,
101 transformStyle: 'preserve-3d'
102 }}
103 className="absolute h-48 w-48 sm:h-60 sm:w-60 md:h-64 md:w-64 rounded-full border border-blue-400/50 [backface-visibility:visible]"
104 />
105 );
106 })}
107
108 <div
109 style={{ transform: 'translateZ(115px)' }}
110 className="absolute h-3.5 w-3.5 rounded-full bg-cyan-400 shadow-[0_0_15px_#22d3ee] ring-4 ring-white/90 animate-ping"
111 />
112 <div
113 style={{ transform: 'rotateY(110deg) translateZ(115px)' }}
114 className="absolute h-3 w-3 rounded-full bg-indigo-400 shadow-[0_0_12px_#818cf8] ring-2 ring-white/70"
115 />
116 <div
117 style={{ transform: 'rotateY(220deg) rotateX(30deg) translateZ(115px)' }}
118 className="absolute h-3 w-3 rounded-full bg-emerald-400 shadow-[0_0_12px_#34d399] ring-2 ring-white/70"
119 />
120 <div
121 style={{ transform: 'rotateY(300deg) rotateX(-20deg) translateZ(115px)' }}
122 className="absolute h-2.5 w-2.5 rounded-full bg-amber-400 shadow-[0_0_12px_#fbbf24] ring-2 ring-white/70"
123 />
124 </div>
125 </div>
126
127 <div className="mt-4 flex items-center gap-2 rounded-full bg-slate-900/90 border border-cyan-500/40 px-4 py-1.5 text-xs font-semibold text-cyan-300 backdrop-blur-md shadow-lg">
128 <span className="h-2 w-2 rounded-full bg-cyan-400 animate-pulse" />
129 <span>Interactive 3D Spatial Globe</span>
130 </div>
131 </div>
132 );
133}

Installation & Integration Guide

Step 1: Tailwind CSS v4 Setup
Tailwind Docs

If you do not have Tailwind installed yet, run this installation command:

Terminal
npm install tailwindcss @tailwindcss/postcss postcss

Then add the Tailwind directive to your main stylesheet (globals.css):

@import "tailwindcss";

Step 2: Install Icon Dependencies

npm install lucide-react react-icons

Step 3: Add Component File

Create a new file in your project at src/components/effects/ThreeDGlobe.tsx and paste the copied code from the source viewer above.

Step 4: Usage Example

ExamplePage.tsx
1import 3DSpatialGlobe from '@/components/effects/ThreeDGlobe';
2
3export default function ExamplePage() {
4 return (
5 <main className="p-8 flex items-center justify-center min-h-screen">
6 <3DSpatialGlobe />
7 </main>
8 );
9}