Advertisement

Draggable 3D Cube with Dynamic Lighting

| by Vladimir | 3 min read | code by Tom Miller
Intermediate

Tech & Dependencies

HTML CSS JavaScript
GSAP GSAP Draggable

Features

  • Simulated Lighting
  • 3D Transforms
  • Inertia Dragging
  • Procedural Geometry

Browser Support

Chrome 90+ Edge 90+ Safari 14+ Firefox 88+

Core

This Draggable 3D Cube goes beyond standard CSS 3D transforms by implementing a custom “lighting engine” using JavaScript. As the user drags to rotate the carousel, the opacity of each face is dynamically calculated based on its angle relative to the viewport. This creates a realistic shading effect - faces facing the viewer are bright and fully opaque, while those turning away fade into the background, enhancing the perception of depth without using WebGL.

Core Technique

The component combines CSS for the 3D structure and GSAP for the interaction and mathematical calculations.

1. The Center-Pivot Construction

Instead of manually translating four faces outward (translateZ) and rotating them, the code simplifies the geometry by moving the transformOrigin. By setting transformOrigin: "50% 50% -150px", the axis of rotation is moved 150px behind the element. Since the cube is 300px wide, this places the pivot point exactly in the volumetric center of the cube. We then only need to rotate the faces (0°, 90°, 180°, 270°) and bring them to the front plane (z: 150).

2. Simulated Lighting via Opacity

The most advanced feature is the lighting simulation inside the onDrag update loop. The goal is to make a face opaque (1) when it is at 0° relative to the viewer, and transparent (0) when it is at 90°.

Since the cube can rotate infinitely (e.g., 720°, -540°), simply using the rotation value won’t work. The code uses gsap.utils.wrapYoyo(0, 90, value). This utility creates a “triangle wave” value that oscillates between 0 and 90 regardless of how large the input number gets.

onUpdate: () => {
  gsap.set('.face', { 
    opacity: (i) => {
      // Get current rotation of the cube container
      const cubeRot = gsap.getProperty(cube, 'rotationY');
      
      // Offset by the face's initial position (i * 90)
      const currentAngle = Math.abs(cubeRot + i * 90);
      
      // Calculate normalized opacity (1 at center, 0 at 90deg turn)
      return 1 - gsap.utils.wrapYoyo(0, 90, currentAngle) / 90;
    }
  });
}

3. Decoupled Drag Layer

The interaction is handled by a separate, invisible #dragger div overlaying the screen. This ensures that the drag event doesn’t get interrupted if the cursor slides off the rotating cube or if pointer-events on the faces interfere with the drag logic.

Accessibility (A11y)

  • Content: The images are CSS backgrounds, meaning screen readers see empty divs.
  • Keyboard: The cube can only be rotated via mouse or touch.
  • Fix: Use semantic <img> tags inside the faces with alt attributes. Implement keyboard event listeners (Left/Right arrows) to trigger gsap.to for rotation.

Browser Support

Relies on standard CSS 3D transforms and GSAP. Works smoothly on all modern browsers.

Advertisement