GODRICH
#css-animation#button#loader#transition#scroll#dashboard#ui#design-trends

3D Globe Website: 9 Canvas Orbit Scenes

A 3d globe website usually loads Three.js. These nine scenes run with no Three.js — just Canvas 2D math — and the widest movers measured 26.98% (08) and 22.07%

Auto-plays · click a tile to jump to its section · all nine in one zip

The order is not a popularity contest; it follows how the sphere math stacks up. First, orthographic projection turns spherical coordinates into screen positions (01). Then dots, curves, and light are computed on top of that sphere (02–05). Then the scene grows past the globe into background and orbits (06, 07), and particles plus a ring close it as one composition (08, 09). The pixel-refraction canvas scene effects and the seeded CSS background patterns are this post's neighbors.

01Rotating globe drawn from latitude/longitude lines

A wire globe for the hero of a global service page, spinning on latitude and longitude lines.

Rotating globe drawn from latitude/longitude lines
Open in a new tab

01 · Rotating globe drawn from latitude/longitude lines — Measured area 14.28% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

orthographic projectionbackface cullingrequestAnimationFrame

Not drawing the back half is what makes it solid

  function polyline(pts, style, width) {
    ctx.strokeStyle = style;
    ctx.lineWidth = width;
    ctx.beginPath();
    var pen = false;
    for (var i = 0; i < pts.length; i++) {
      var p = pts[i];
      if (p.z >= 0) {
        var x = cx + p.x * R, y = cy - p.y * R;
        if (pen) ctx.lineTo(x, y); else { ctx.moveTo(x, y); pen = true; }
      } else {
        pen = false;   // lift the pen where the line crosses to the back
      }
    }
    ctx.stroke();
  }

Each point is rotated by axis tilt and spin angle, projected to the screen, and skipped whenever depth z goes negative. Hovering the board eases the axis toward a target tilt at 12% per frame (measured: 14.28% cumulative area, 23/23 frames moving).

02Dot-matrix globe filled with points

A globe of 850 dots for a "visitors worldwide" section.

Dot-matrix globe filled with points
Open in a new tab

02 · Dot-matrix globe filled with points — Measured area 10.422% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

Fibonacci sphereseeded randomdepth sort

The same seed draws the same globe

// deterministic PRNG — mulberry32. Same seed, same stream
function mulberry32(a) {
  return function () {
    a |= 0; a = (a + 0x6D2B79F5) | 0;
    var t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 429497296;
  };
}

The 850 dots sit on a Fibonacci sphere so the poles never clump, and each dot's radius, brightness, and twinkle phase come from the seeded stream. Type a number into the seed input and the layout is redrawn deterministically — the same number returns the exact same picture (measured: seed 42 → 7 regenerates every dot position).

03Satellite orbits circling the globe

Satellites on elliptical orbits for a space or telecom service page.

Satellite orbits circling the globe
Open in a new tab

03 · Satellite orbits circling the globe — Measured area 14.463% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

elliptical orbitpainter's algorithmz-sort

Painter's algorithm — paint back to front

  // ── stage 2: sort the globe + satellites together by z (painter's algorithm)
  var items = [{ kind: 'globe', z: 0 }];
  for (var m = 0; m < sats.length; m++) {
    items.push({ kind: 'sat', z: sats[m].p.z, ref: sats[m] });
  }
  items.sort(function (a, b) { return a.z - b.z; });   // back (small z) first — ascending z-sort

The globe and every satellite share one array sorted by depth z and drawn back to front, so a satellite passing behind the globe is covered and one crossing the front sits on top. The far half of each orbital ellipse is dimmed, and every satellite runs at its own speed so the orbits drift apart.

04Flight-path arcs connecting cities

The signature scene of a global shipping or airline page: glowing routes between cities.

Flight-path arcs connecting cities
Open in a new tab

04 · Flight-path arcs connecting cities — Measured area 10.153% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

great-circle arcBezier interpolationquadratic curve

Spherical linear interpolation draws the shortest path

// slerp — spherical linear interpolation. omega is the central angle between the two points
function slerp(p0, p1, t) {
  var dot = p0.x * p1.x + p0.y * p1.y + p0.z * p1.z;
  dot = Math.max(-1, Math.min(1, dot));
  var omega = Math.acos(dot);
  if (omega < 1e-5) return { x: p0.x, y: p0.y, z: p0.z };
  var so = Math.sin(omega);
  var k0 = Math.sin((1 - t) * omega) / so;
  var k1 = Math.sin(t * omega) / so;
  return { x: p0.x * k0 + p1.x * k1, y: p0.y * k0 + p1.y * k1, z: p0.z * k0 + p1.z * k1 };
}

Take two cities as points on a unit sphere and connect them with slerp for a great-circle route that never tunnels through the planet. The arc floats at 1.10 times the globe radius so the curve rests on the surface instead of pressing into it, and a glowing dot travels each arc to show flights in progress.

05Globe split by a day/night terminator

A globe that brightens toward the sun, for a time-zone or global-hours widget.

Globe split by a day/night terminator
Open in a new tab

05 · Globe split by a day/night terminator — Measured area 14.608% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

dot product shadingsun vectorradial gradient

Every pixel dots its normal with the sun

      var nx = dx / R, ny = -dy / R;
      var nzsq = 1 - nx * nx - ny * ny;
      if (nzsq < 0) continue;
      var nz = Math.sqrt(nzsq);
      var ndotl = nx * sun.x + ny * sun.y + nz * sun.z;   // normal · sun
      var lit = Math.max(0, ndotl);
      var t = Math.min(1, lit * 2.2);                     // widen the rim into a gradient

Instead of stacking CSS gradients, this fills an ImageData every frame and computes the surface normal per pixel, so the terminator bends around the sphere as the sun moves (measured: intensity 19.4, 23/23 frames).

06Layered starfield background with parallax

A dark hero backdrop of layered stars for a space-concept landing page.

Layered starfield background with parallax
Open in a new tab

06 · Layered starfield background with parallax — Measured area 22.067% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

seeded randomparallax layerstwinkle noise

The seed decides the sky

  // 3 layers — back (slow · small · many) to front (fast · big · few)
  var defs = [
    { n: 110, par: 0.35, size: [0.6, 1.4], drift: 4 },
    { n: 70,  par: 0.75, size: [1.0, 2.0], drift: 9 },
    { n: 34,  par: 1.25, size: [1.5, 2.8], drift: 16 }
  ];

The same seed guarantees the same sky, and 214 stars split across three layers with different parallax factors make distant stars crawl and near stars sweep as the pointer moves. The seed input and the new-sky button redraw the field, twinkle phases included, and it measured 22.07% cumulative area — second widest of the nine.

07Orrery model with several orbiting planets

An orrery that obeys Kepler's second law, for education content or an exhibition page.

Orrery model with several orbiting planets
Open in a new tab

07 · Orrery model with several orbiting planets — Measured area 10.285% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

Kepler's second laworbital periodellipse parametric

Equal areas in equal times

// r(theta) — polar equation of an ellipse with the focus (the sun) at the origin
function radiusAt(p, theta) {
  return p.a * (1 - p.e * p.e) / (1 + p.e * Math.cos(theta));
}

function step(dt) {
  for (var i = 0; i < PLANETS.length; i++) {
    var p = PLANETS[i];
    var r = radiusAt(p, p.theta);
    var dtheta = p.L / (r * r);          // Kepler II: dA/dt = (1/2) r^2 dtheta/dt = const = L/2
    p.theta = (p.theta + dtheta * dt) % TAU;
  }
}

Each planet's speed is computed as area velocity, not angular velocity, so it races near perihelion and drags near aphelion — and the swept fan left behind on the orbit shows the equal-areas law as a picture. Five orbits with different periods and eccentricities drift out of phase.

08Particles that scatter and assemble into a globe

Scattered particles converging into a globe, for a brand intro or loading scene.

Particles that scatter and assemble into a globe
Open in a new tab

08 · Particles that scatter and assemble into a globe — Measured area 26.978% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

lerp interpolationeasing functiontarget assignment

Easing toward target positions

// cycle timeline (seconds) — hold(scattered) -> assemble -> hold(sphere) -> disperse -> repeat
var T_HOLD_OUT = 1.2, T_ASSEMBLE = 2.6, T_HOLD_IN = 2.6, T_DISPERSE = 2.6;
var CYCLE = T_HOLD_OUT + T_ASSEMBLE + T_HOLD_IN + T_DISPERSE;

function phaseOf(clock) {
  var c = clock % CYCLE;
  if (c < T_HOLD_OUT) return { k: 'out', t: 0 };
  c -= T_HOLD_OUT;
  if (c < T_ASSEMBLE) return { k: 'assemble', t: easeInOut(c / T_ASSEMBLE) };
  c -= T_ASSEMBLE;
  if (c < T_HOLD_IN) return { k: 'in', t: 1 };
  c -= T_HOLD_IN;
  return { k: 'disperse', t: easeInOut(c / T_DISPERSE) };
}

Each of the 260 particles eases from a random scatter point to a Fibonacci-sphere target, sized and faded by depth z so the orthographic volume reads clearly. It moved the most of all nine at 26.98% cumulative area (measured: intensity 58.6, 23/23 frames) while drawing only 260 dots per frame.

09Globe wrapped in a ring halo

A ringed globe to close a premium service page.

Globe wrapped in a ring halo
Open in a new tab

09 · Globe wrapped in a ring halo — Measured area 19.972% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

ellipse projectionocclusion splitperspectiveScale

Split front and back to create occlusion

// 1) back half of the ring (the half passing behind the sphere)
drawRingHalf(ctx, cx, cy, R, C_ring, C_ringHi, false);

// 2) the globe — hemisphere shading
var g = ctx.createRadialGradient(cx - R * 0.35, cy - R * 0.4, R * 0.1, cx, cy, R);
g.addColorStop(0, C_deep);
g.addColorStop(1, C_sphere);
ctx.beginPath();
ctx.arc(cx, cy, R, 0, TAU);
ctx.fillStyle = g;
ctx.fill();

// 3) front half of the ring (the half crossing in front) — covers the globe, finishing the wrap
drawRingHalf(ctx, cx, cy, R, C_ring, C_ringHi, true);

Every point of the tilted elliptical ring rotates with the globe's own spin and tilt, and the sign of depth z decides front from back. Back half, globe, front half — a painter's-algorithm variant that makes the ring physically wrap the sphere, with the ring's middle band picked out in a brighter highlight tone (measured: 19.97% cumulative area).

Why draw a globe with canvas coordinate math?

Most 3d globe website effects delegate to a library like Three.js, but every scene here needs exactly one line of orthographic projection to turn spherical coordinates into screen positions. Rotation is two values, cos and sin, spinning the axis; occlusion is a draw order picked from depth z; day/night shading is a dot product of the normal and sun vectors. With no library, there are zero files to load, and because everything draws into one canvas, any page can lend these scenes its own color tokens.

In measurement all nine scenes moved in all 23 sample frames (alive), with cumulative area running from 10.15% (04) up to 26.98% (08) — 06 (22.07%) and 08 sweep the widest as star and particle fields, while the globe-centered scenes turn with restraint. Time came straight from requestAnimationFrame timestamps and held 60fps throughout.

Where it breaks — satellites tunneling through the globe

The first version of 03 drew the orbital ellipse flat on screen and computed each satellite's position with a 2D ellipse equation. The globe was just a circle, and nothing compared the satellite's depth against it, so satellites cheerfully tunneled through the planet on every pass. Only after positions were computed on a 3D orbit and thrown into one z-sorted array with the globe did the occlusion look natural.

The 05 terminator had its own first-version failure: CSS gradients stacked on a circle keep the boundary at the same screen angle no matter where the sun is, which reads as a half-lit sticker rather than a planet. The full source of all nine scenes and their React ports is in the password archive below, and the password is btsc7vfs.

Three variants and accessibility

Name Value changed Feel
Dot density 02 N 850 → 1400 The globe reads as a denser, more solid mass
Parallax strength 06 par 0.35/0.75/1.25 → 0.2/0.5/1.6 Layer separation grows; depth turns dramatic
Spin speed 09 rotY 0.26 → 0.6 the ring and globe sweep around like a shorter Saturn day

For accessibility, all nine boards share one rule: with prefers-reduced-motion: reduce, the rAF loop never starts and each board draws the single static frame it reads best in — 01 keeps the finished wire globe, 05 the split terminator, 08 the assembled sphere. Boards 02 and 06 carry a seed input with tabindex and role so the seed can be driven from the keyboard, and every screen label ships in three languages.

FAQ

Can an interactive 3d globe website scene like these stay fast without a library?

Yes. These scenes draw only lines and dots per frame, and the heaviest one (05, the per-pixel pass) walks only the inside of the circle. Canvas size follows its board and scales by devicePixelRatio alone, so retina screens keep the same computation count.

Is 03 a real orbital simulation like a css globe animation gone physical?

The orbit shapes run on ellipse parameters with fixed per-satellite speed offsets — an approximation. If you need physical accuracy, lift the Kepler area-velocity math from 07 onto 03 and satellites will accelerate at perihelion.

Can I paste the source straight into my page?

Yes. The archive below holds the vanilla and React versions of all nine scenes, and they run as-is once pasted. Colors swap through the token variables at the top of each scss file, and the seed inputs ship as-is in 02 and 06.

Further reading

Enter the archive password

The password is inside this article. You will find it as you read.