webdevpuneet.com

Three.js Solar System — Draggable Orbiting Planets in WebGL

Three.js Solar System · Animations · Plain HTML, CSS & JS · Live preview

CategoryAnimations

Copy & paste

Source Code

<canvas id="solarCanvas"></canvas>
<button id="solarPause" class="solar-pause">⏸ Pause orbits</button>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Object3D pivot orbits: rotating an invisible parent sweeps each offset planet mesh through a perfect circle
Nested moon pivot: a second pivot inside the planet mesh orbits the moon while automatically following the planet
Dotted orbit path rings: 128-point THREE.Line circles trace every planet's orbit radius in world space
Tilted RingGeometry: a flat annulus parented to its planet renders a Saturn-like ring with DoubleSide shading
OrbitControls from a CDN: damped drag-to-rotate and bounded scroll-zoom camera with no bundler
Independent spin and orbit speeds: every planet rotates on its own axis at a different rate than it orbits
Real pause state: a single boolean halts orbital motion while camera controls keep responding
Emissive sun with an attached point light: illuminates every planet from a single moving light source

About this UI Snippet

How to Build an Orbiting Three.js Solar System With Object3D Pivots

Screenshot of the Three.js Solar System snippet rendered live

The Three.js Solar System snippet renders a glowing sun with six planets — one carrying a moon, one carrying a ring — all orbiting continuously at their own speeds, with a draggable camera and a pause button, using core Three.js plus its OrbitControls addon loaded from a CDN.

Object3D pivots do the orbit math, not manual trigonometry

The single most important idea in this snippet: each planet's mesh is offset along the X axis from an invisible THREE.Object3D pivot positioned at the origin, and the *pivot* is what actually gets rotated every frame — not the planet's own position. Because the mesh is a child of the pivot in the scene graph, rotating the parent automatically sweeps the offset child mesh through a perfect circular orbit. This completely replaces manually computing x = cos(angle) * radius, z = sin(angle) * radius by hand — Three.js's scene graph transform hierarchy does that math for free, and it's the standard technique for orbital motion in any 3D engine.

Moons nest a second pivot inside the planet

The planet with a moon carries a second Object3D pivot as a *child of the planet mesh itself*, with the moon offset from that inner pivot the same way the planet is offset from its own outer pivot. Rotating the inner pivot orbits the moon around the planet; because the moon-pivot is nested inside the already-orbiting planet, the moon automatically follows the planet around the sun too, with zero extra code to keep them together. This nested-pivot pattern is exactly how you'd extend the scene to add sub-moons, orbiting space stations, or any hierarchy of things-orbiting-things.

Dotted orbit paths use one Line per ring

Each planet's orbit radius is traced once at startup as a THREE.Line built from 128 points sampled around a circle — a static, unmoving ring drawn directly into world space (not attached to any pivot). This lets the visitor see exactly how far out each planet's orbit reaches even when the planet itself is hidden behind the sun or on the far side of the scene.

A ring is just a flat RingGeometry, tilted and parented to its planet

The outermost, ringed planet adds a THREE.RingGeometry — a flat annulus, not a torus — as a child of the planet mesh, tilted on its X axis and rendered with side: THREE.DoubleSide so it doesn't disappear when viewed edge-on or from below. Because it's parented to the planet mesh, the ring automatically follows the planet's orbit and spin without any additional transform logic.

Draggable camera via OrbitControls, bounded so you can't fly through the sun

A second CDN script, Three.js's OrbitControls addon, attaches drag-to-rotate and scroll-to-zoom camera behavior. minDistance and maxDistance keep the visitor from zooming in past the sun or zooming out until the whole system becomes a speck, and enableDamping makes every drag glide smoothly to a stop rather than snapping to a halt the instant the mouse button releases.

A real pause button, not just a visual toggle

Clicking "Pause orbits" sets a single boolean that the animation loop checks before advancing any rotation — the camera itself keeps responding to drags and OrbitControls' damping keeps updating even while paused, so the scene never feels frozen or unresponsive, only the orbital motion stops.

Where this technique scales

The pivot-per-orbiting-object pattern used here is the same one behind any "things circling other things" 3D scene — compare it with the flatter, ring-and-bead approach in orbit rings, or contrast the physically-accurate orbital hierarchy here against the purely decorative circular motion of a CSS-based orbiting icons snippet.

Step by step

How to Use

  1. 1
    Load both CDN scriptsAdd three.min.js and OrbitControls.js from the CDN panel, in that order.
  2. 2
    Paste HTML, CSS, and JSThe sun and six planets appear immediately, orbiting at their own speeds.
  3. 3
    Drag to look aroundClick and drag to orbit the camera; scroll or pinch to zoom, bounded between the sun and the outer orbit.
  4. 4
    Pause and resumeClick the pause button to freeze all orbital motion; camera dragging still works while paused.
  5. 5
    Adjust planet dataEdit the PLANETS array to change distance, size, color, orbit speed, or add a moon or ring to any planet.
  6. 6
    Resize the windowRenderer size and camera aspect ratio update automatically.

Real-world uses

Common Use Cases

Teaching orbital mechanics or Object3D hierarchy
A concrete, visual demonstration of parent-child transforms — one of the most important concepts in any 3D scene graph.
Space, astronomy, and education sites
A draggable, explorable solar system fits science education platforms, planetariums, and space-themed products directly.
Portfolio and agency showpieces
Demonstrates real 3D scene-graph competence, not just a static render — a strong differentiator in a portfolio piece.
Game menu and loading backgrounds
An explorable orbital system gives a loading or menu screen something engaging to interact with while waiting.
Hierarchical data visualization
Repurpose the pivot-and-orbit pattern to visualize any parent-child dataset as orbiting bodies instead of a tree diagram.
Presentation and pitch-deck backgrounds
A slowly orbiting system makes a compelling, low-distraction animated background behind a fixed headline or logo.

Got questions?

Frequently Asked Questions

Each planet mesh is offset along the X axis from an invisible Object3D pivot located at the origin. Rotating that pivot every frame automatically sweeps the offset child mesh through a circular path, because Three.js applies the parent's rotation to all of its children. This replaces manual cos/sin position math with the scene graph's built-in transform hierarchy.

The moon has its own Object3D pivot, but that pivot is a child of the planet's mesh rather than the scene root. Because the moon-pivot inherits every transform applied to its parent planet, rotating the planet's own orbit pivot carries the moon along automatically, while independently rotating the inner moon-pivot orbits the moon around the planet.

The dotted orbit rings are static circles in world space — they never move — so they're added directly to the scene root rather than to any pivot. Keeping them separate from the animated pivots means they always show the true, unmoving orbit radius even while planets circle around them.

The ring is a THREE.RingGeometry, a flat annulus, added as a child of the planet mesh and tilted on its X axis. Its material sets side: THREE.DoubleSide because a flat ring viewed edge-on or from below would otherwise render as invisible — by default Three.js only draws the front face of a surface.

No. The pause button only stops the orbital rotation logic inside the animation loop. OrbitControls.update() is still called every frame regardless of the pause state, so dragging to look around and the damped deceleration after a drag both keep working normally.

Yes. Click JSX for a React component, Vue for a Vue 3 SFC, Angular for a standalone component, or Tailwind for a React + Tailwind CSS utility-class version. Build the pivots, planets, and controls inside a mount effect, keep the paused flag in a ref (not React state) so the animation loop can read it without triggering re-renders, and call controls.dispose() plus renderer.dispose() on cleanup.