GAMES IN HTML5

Three.js 3D Game Level Selection — Draggable 3D Carousel with Momentum

This is how game character select and level select screens should feel. Drag left or right and 8 panels orbit around a virtual cylinder — the panel in front depth-focuses, drag-release applies momentum that coasts to a stop, and the nearest panel snaps to front. Zero carousel libraries, just Three.js, trigonometry, and pointer events. Build your game's selection screen in an afternoon.

What you will build

A 3D cylindrical carousel where 8 panels are placed around a circle using trigonometry. Drag velocity is applied as angular momentum with a 0.92 friction factor, so the carousel decelerates naturally and the closest panel snaps to front using a short easing interpolation.

How the Three.js 3D carousel drag works

Position N panels at angle = (i / N) * Math.PI * 2 around a radius using Math.sin(angle) * radius for X and Math.cos(angle) * radius for Z. On pointermove, measure the horizontal delta between the current and previous pointer positions and add it to a rotationSpeed variable. Each frame, add rotationSpeed to the group's rotation.y and multiply rotationSpeed by the friction factor (0.92).

The panel closest to the camera (the one with rotation.y ≈ 0) gets a scale of 1.15 to visually pop it forward. Compute which panel is "in front" by finding the one whose world angle is closest to 0 after normalizing for the group rotation. After drag ends, lerp the rotation toward the nearest snap angle to settle it cleanly.

scene.jsKEY IDEA
const angle = index / cardCount * Math.PI * 2;

card.position.set(
  Math.sin(angle) * radius,
  0,
  Math.cos(angle) * radius
);

card.lookAt(0, 0, 0);

The cylinder layout is six trig lines. Drag physics is two lines: add delta to speed, multiply speed by friction each frame. The rest is Three.js scene setup.

Why Three.js for a carousel instead of CSS-only

CSS-only carousels with scroll-snap are excellent for flat layouts. Three.js is justified when you need perspective depth, per-panel 3D transforms, custom lighting on panels (for product showcase), or drag-with-momentum that feels physically natural rather than scroll-snappy. The 3D cylindrical layout is impossible in CSS without a fixed panel count and manually computed rotateY values per item.

This carousel also handles variable panel content naturally: each Mesh can have its own texture, color, or geometry. Add THREE.MeshStandardMaterial with a map texture and the panels become a product gallery with real lighting. The drag and momentum code stays identical regardless of what's on the panels.

Performance note

Keep panel geometry low-poly—each panel is a PlaneGeometry(2, 2.5, 1, 1) (4 vertices). Texture maps should be power-of-two dimensions (512×512, 1024×512) for optimal GPU memory alignment. Dispose of unused textures with texture.dispose() when panels scroll out of view permanently.

Extensions and variations

Add a raycaster click handler to detect which panel the user taps and trigger a view transition to a detail page. Replace flat planes with angled panels for a fan-carousel style. Add a subtle vertical parallax by moving the camera Y position slightly based on the device gyroscope for an AR-like depth effect on mobile.

Frequently asked questions

Can I use this 3D carousel with real images or video?

Yes. Pass a THREE.TextureLoader texture to MeshStandardMaterial.map for images, or a THREE.VideoTexture for video panels. The carousel code is geometry-agnostic—the positioning and drag logic work regardless of what the material shows.

How do I snap to the nearest panel after release?

After the drag ends (on pointerup), find the panel index whose world angle is closest to 0. Compute the target rotation as -(index / N) * Math.PI * 2. Lerp the group rotation toward that target each frame at a factor of 0.08–0.12 until the difference is below 0.001.

Related experiments

Part of a weekly series on visual JavaScript, modern CSS, browser graphics, and HTML5 games—one live experiment per post, production-ready code snippets, and the reasoning behind each technique.

ADVERTISEMENT
ADVERTISEMENT

No comments:

Post a Comment

Search the blog

Blog archive

Latest in English