Home

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