Three.js Orbit Rings — Draggable WebGL Orbital System

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

Share & Support

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.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You do not have to trace the tilt-versus-orbit math by hand to understand it. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why each ring's bead position math never needs to account for its group's tilt, or how OrbitControls' enableDamping actually smooths out camera movement between frames. The same assistant can help optimize it, for instance checking whether the six ring geometries could share a single instanced draw call, or whether the torus segment counts could be lowered on mobile without a visible quality loss. It is also useful for extending the scene: ask it to add a second bead per ring traveling in the opposite direction, make ring color or speed respond to scroll position, or replace the beads with small orbiting planet meshes to turn this into a solar-system-style scene. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a "draggable orbit rings" scene in plain HTML, CSS, and JavaScript using Three.js and its OrbitControls addon, both loaded from a CDN (no bundler, no build step).

Requirements:
- A full-viewport canvas with a WebGLRenderer sized to match it, updated on window resize including camera aspect ratio.
- Load core Three.js from a CDN, then load the OrbitControls addon script from a CDN afterward so it attaches to the global THREE namespace; instantiate OrbitControls on the camera and renderer's DOM element with damping enabled, a bounded min/max zoom distance, and idle auto-rotation enabled.
- A glowing central mesh (an icosahedron or similar faceted shape) with an emissive material and at least one point light plus ambient light in the scene.
- Six concentric ring objects, each built from a thin wireframe or low-opacity torus geometry at a different radius, each wrapped in its own group whose rotation is set once at page load to a random tilt on two axes so the rings are not all parallel.
- Inside each ring's group, add a small glowing sphere ("bead") whose local position is recalculated every animation frame using cosine and sine of an angle derived from a shared time value, each ring's own orbit speed, and a random per-ring phase offset — so every ring's bead travels at a visibly different speed and starting position.
- Call the OrbitControls update method once per animation frame so damping and auto-rotate function correctly, and render the scene after that call.
- Do not hardcode the ring tilts to fixed values — they must be randomized once at startup so the arrangement differs on every page load.

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.