Three.js: Spinning Globe with Pulsating Markers
An interactive 3D globe visualization built with three.js that displays a rotating Earth with pulsating markers showing major stock exchange locations.
Features
- Rotating Earth with realistic texture
- Interactive camera controls (orbit, zoom)
- Pulsating markers at geographic coordinates
- Smooth animations with time-based updates
- Responsive design
Core Components
Scene Setup
The scene uses a dark background with two light sources for realistic globe rendering:
1const scene = new THREE.Scene();
2scene.background = new THREE.Color(0x0b0c0f);
3
4scene.add(new THREE.DirectionalLight(0xffffff, 1.2).position.set(5, 3, 5));
5scene.add(new THREE.AmbientLight(0xffffff, 0.4));
Globe Creation
The globe is a sphere with an Earth texture applied:
1const textureLoader = new THREE.TextureLoader();
2const earthTex = await textureLoader.loadAsync(
3 "https://threejs.org/examples/textures/planets/earth_atmos_2048.jpg",
4);
5if (THREE.SRGBColorSpace) earthTex.colorSpace = THREE.SRGBColorSpace;
6
7const globe = new THREE.Mesh(
8 new THREE.SphereGeometry(1, 64, 64),
9 new THREE.MeshPhongMaterial({ map: earthTex }),
10);
11scene.add(globe);
Geographic Coordinate Conversion
Convert latitude/longitude to 3D Cartesian coordinates:
1function toXYZ(lat, lon, r = 1.01) {
2 const phi = ((90 - lat) * Math.PI) / 180;
3 const theta = ((lon + 180) * Math.PI) / 180;
4 return {
5 x: -r * Math.sin(phi) * Math.cos(theta),
6 y: r * Math.cos(phi),
7 z: r * Math.sin(phi) * Math.sin(theta),
8 };
9}
Parameters:
lat: Latitude in degrees (-90 to 90)lon: Longitude in degrees (-180 to 180)r: Radius (default 1.01 to place markers slightly above surface)
Returns: Object with x, y, z coordinates
Marker Placement
Markers are positioned at geographic coordinates and oriented to face outward:
1const exchanges = [
2 { name: "NYSE", lat: 40.7069, lon: -74.0113 },
3 { name: "LSE", lat: 51.5074, lon: -0.1278 },
4 // ... more locations
5];
6
7exchanges.forEach((ex) => {
8 const { x, y, z } = toXYZ(ex.lat, ex.lon);
9 const m = new THREE.Mesh(markerGeom, markerMat);
10 const normal = new THREE.Vector3(x, y, z).normalize();
11 m.position.set(x, y, z);
12 m.quaternion.setFromUnitVectors(new THREE.Vector3(0, 0, 1), normal);
13 globe.add(m);
14 markers.push(m);
15});
Animation Loop
The animation handles rotation and pulsation:
1function animate(now) {
2 const dt = (now - last) / 1000;
3 last = now;
4 globe.rotation.y += 0.2 * dt; // Rotation speed
5
6 const t = now * 0.002; // Pulsation timing
7 markers.forEach((m) => {
8 const s = 1 + 0.3 * Math.sin(t * 2 * Math.PI);
9 m.scale.setScalar(s);
10 });
11
12 controls.update();
13 renderer.render(scene, camera);
14 requestAnimationFrame(animate);
15}
Customization
Adjust Rotation Speed
Change the rotation multiplier in the animate function:
1globe.rotation.y += 0.5 * dt; // Faster rotation
Modify Pulsation
Adjust the amplitude and frequency:
1const s = 1 + 0.5 * Math.sin(t * 3 * Math.PI); // Faster, larger pulses
Change Marker Appearance
Customize the geometry and material:
1const markerGeom = new THREE.CircleGeometry(0.03, 32); // Larger markers
2const markerMat = new THREE.MeshBasicMaterial({ color: 0xff0000 }); // Red markers
Add More Locations
Extend the exchanges array with new coordinates:
1{ name: "TSX", lat: 43.6532, lon: -79.3832 } // Toronto Stock Exchange
Controls
- Drag: Orbit around the globe
- Wheel: Zoom in/out
- Damping: Smooth camera movement
Dependencies
- three.js r160 (via CDN)
- OrbitControls module
Notes
- Markers are parented to the globe, so they rotate with it
- The coordinate conversion uses standard geographic conventions
- Texture loading is asynchronous (uses await)
- Responsive design handles window resizing
- PixelRatio is capped at 2 for performance