webdevpuneet.com

Three.js Orbit Rings — Draggable WebGL Orbital System

Three.js Orbit Rings · Animations · Plain HTML, CSS & JS · Live preview

CategoryAnimations

Copy & paste

Source Code

<canvas id="ringsCanvas"></canvas>
<div class="or-hint">Drag to orbit · Scroll to zoom</div>

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

OrbitControls from a CDN: mouse-drag rotation and scroll-zoom with no bundler required
Grouped tilt-plus-orbit: each ring's static tilt and its bead's live orbit math stay fully decoupled
Traveling orbit beads: glowing spheres circling each ring sell direction and speed, not just a static shape
Randomized startup tilt: every page load produces a genuinely different ring arrangement
Damped camera controls: enableDamping makes drags glide to a stop instead of snapping
Bounded zoom range: minDistance and maxDistance keep the camera from clipping through or flying away
Idle auto-rotation: the whole system spins gently on its own whenever nobody is dragging
Emissive icosahedron core: faceted, glowing center reinforces the orbital-system read

About this UI Snippet

How to Build a Draggable Three.js Orbit Ring System

Screenshot of the Three.js Orbit Rings snippet rendered live

The Three.js Orbit Rings snippet renders six independently tilted, independently spinning wireframe rings around a glowing core — an atom-like or satellite-network visual — and lets the visitor drag to rotate the whole scene and scroll to zoom, using Three.js's OrbitControls addon loaded straight from a CDN alongside the core library.

Grouping tilt and orbit separately

Each ring is wrapped in its own THREE.Group, and the group itself is rotated to a random tilt angle once, at startup. The ring mesh and its traveling bead are added as children of that tilted group. This separation matters: the group's rotation handles the *static* tilt of the whole ring in 3D space, while the bead's position is recalculated every frame purely in the group's own local 2D plane using Math.cos/Math.sin. Because the bead's orbit math never has to account for the tilt directly, adding six differently-tilted rings costs nothing extra in complexity — the parent group's rotation matrix handles that transformation automatically.

A traveling bead gives orbits direction and speed

A static wireframe torus reads as a flat ring, not an orbit. Each ring carries a small glowing sphere whose position is recomputed every frame as (cos(angle) × radius, 0, sin(angle) × radius), where angle combines the shared animation clock with a per-ring speed and phase offset. Six rings orbiting at six different speeds — some fast, some barely moving — is what sells the "orbital system" read rather than "concentric decorative circles."

OrbitControls from a CDN, not a bundler

The scene loads a second script — Three.js's OrbitControls.js addon — directly from jsDelivr after the core three.min.js script, which attaches THREE.OrbitControls onto the global THREE namespace. This is the standard way to add mouse-drag camera control to a plain-script Three.js scene without a bundler: enableDamping makes drags glide to a smooth stop instead of snapping, minDistance/maxDistance bound how far the visitor can zoom in or out, and autoRotate keeps the whole system slowly spinning on its own whenever nobody is actively dragging.

Randomized tilt keeps it from looking engineered

Every ring's tilt is set with Math.random() * Math.PI on two axes at startup, which is what stops the scene from reading like a technical diagram of perfectly stacked, parallel rings. Rerunning the snippet produces a genuinely different arrangement each time, since the randomization happens once when the page loads rather than being hardcoded.

A glowing icosahedron core, not a plain sphere

The center object is a low-subdivision IcosahedronGeometry with an emissive MeshStandardMaterial, giving it faceted, crystalline highlights rather than a smooth, featureless sphere — a small detail that reads as more "energy source" than "billiard ball," reinforcing the orbital-system metaphor the rings are already building.

Where this technique fits

The tilted-group-plus-orbiting-child pattern generalizes well beyond rings: swap the bead for a small planet mesh and you have the beginning of a solar system model, or swap the ring for a path and you have an orbit-camera product viewer. Pair this snippet with a particle network for a two-scene "cosmic" section, or contrast it against the flat, CSS-driven motion of an orbiting icons snippet to show the difference between 2D and true 3D orbital motion.

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 JSSix tilted rings and a glowing core appear immediately, slowly auto-rotating.
  3. 3
    Drag to orbitClick and drag anywhere on the canvas to rotate the camera around the scene manually.
  4. 4
    Scroll to zoomMouse wheel or pinch zooms in and out, bounded by minDistance and maxDistance.
  5. 5
    Adjust ring count and speedChange RING_COUNT, radius spacing, or each ring's speed for a sparser or busier system.
  6. 6
    Toggle auto-rotateSet controls.autoRotate to false if you only want manual dragging with no idle spin.

Real-world uses

Common Use Cases

Product and SaaS hero scenes
A draggable orbital system is a strong, interactive hero centerpiece, especially for AI, data, or infrastructure products.
Portfolio and agency showpieces
Demonstrates genuine 3D and interaction skill far more convincingly than a static image or CSS-only animation.
Teaching OrbitControls setup
A minimal, focused example of wiring up camera-drag controls from a CDN alongside core Three.js.
System and network visualizations
Repurpose the ring-and-bead pattern to represent services, nodes, or dependencies orbiting a central system.
Loading and title screens
An interactive orbital scene gives a loading screen something worth dragging around while assets load.
Space and astronomy content
Pairs naturally with a solar system scene or a starfield backdrop for space-themed sites.

Got questions?

Frequently Asked Questions

Each ring and its bead live inside a THREE.Group whose rotation is set once at startup to a random tilt. The bead's position is recalculated every frame purely in local X/Z coordinates using cosine and sine of an angle — it never needs to know about the tilt, because the parent group's rotation matrix applies that transformation automatically when rendering.

Load the OrbitControls.js addon script from a CDN after the core three.min.js script — it attaches THREE.OrbitControls to the global THREE object. Instantiate it with new THREE.OrbitControls(camera, renderer.domElement), then call controls.update() once per animation frame for damping and auto-rotate to work correctly.

Every ring is created with its own speed value (slightly slower for rings further from the core) and a random phase offset. Six rings all sharing one uniform speed would read as a mechanical, single-frequency animation; varied speeds and radii sell the layered, orbital-system look.

Yes. Set controls.autoRotate to false to disable it entirely, so the camera only moves when the visitor actively drags. You can also lower controls.autoRotateSpeed for a slower idle spin instead of removing it outright.

Yes. Raise RING_COUNT and extend the colors array with additional hex values; the radius spacing and per-ring speed formulas scale automatically to any count.

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. Create the renderer, controls, and rings inside a mount effect, call controls.update() and renderer.render() inside your animation loop, and call controls.dispose() plus renderer.dispose() on cleanup so event listeners and the WebGL context don't leak.