Draggable 3D Cube with Dynamic Lighting
See the Pen Draggable 3D Cube with Dynamic Lighting.
Tech & Dependencies
Features
- ✓ Simulated Lighting
- ✓ 3D Transforms
- ✓ Inertia Dragging
- ✓ Procedural Geometry
Browser Support
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 withaltattributes. Implement keyboard event listeners (Left/Right arrows) to triggergsap.tofor rotation.
Browser Support
Relies on standard CSS 3D transforms and GSAP. Works smoothly on all modern browsers.


