Draggable 3D Parallax Image Ring
See the Pen Draggable 3D Parallax Image Ring.
Tech & Dependencies
Features
- ✓ 3D CSS Transforms
- ✓ GSAP Draggable
- ✓ Parallax Backgrounds
- ✓ Infinite Looping
Browser Support
Core
This Draggable 3D Parallax Image Ring creates a high-end, immersive gallery experience often associated with WebGL, but built entirely with DOM elements and GSAP. It arranges images in a 3D circle that users can spin with a swipe or mouse drag. The standout feature is the calculated parallax effect: as the ring rotates, the background position of each image shifts in the opposite direction, creating a convincing illusion of depth and window-like transparency.
Core Technique
The component leverages the power of CSS 3D transforms for structure and GSAP for interaction logic.
1. Constructing the Ring via transformOrigin
Instead of calculating complex (x, z) coordinates for every image using trigonometry (Math.sin, Math.cos), the code uses a clever CSS trick. By setting the transformOrigin to 50% 50% 500px (where 500px is the radius), the origin point is pushed far behind the element.
When the element is rotated (rotateY), it swings around that distant center point, naturally forming a circle.
.set('.img', {
// Distribute 10 images around 360 degrees (36 deg each)
rotateY: (i) => i * -36,
// Push the pivot point 500px back along the Z-axis
transformOrigin: '50% 50% 500px',
// Move the element itself back to correct visual perspective
z: -500,
// ...
})
2. The Invisible Dragger
To avoid issues with 3D hit-testing (where clicking a rotated element might behave unexpectedly), the interaction is decoupled from the visuals. An invisible #dragger layer sits on top of everything. Draggable.create monitors this layer. When the user drags, the script calculates the delta (clientX) and applies rotation to the visual #ring element.
3. Calculated Parallax
The parallax effect creates the illusion that the images are “windows” into a stationary background. This is achieved by updating the backgroundPosition of every image on every frame of the drag.
The getBgPos function uses gsap.utils.wrap to ensure the calculations loop seamlessly between 0 and 360 degrees, translating rotation degrees into pixel offsets.
function getBgPos(i) {
return (
// Wrap ensures smooth looping when rotation goes beyond 360
-gsap.utils.wrap(
0,
360,
gsap.getProperty(ring, 'rotationY') - 180 - i * 36
) / 360 * 400
) + 'px 0px';
}
Accessibility (A11y)
- Content: Images are loaded as
background-imagein CSS. Screen readers will perceive the gallery as 10 emptydivs. - Interaction: The interaction relies entirely on mouse drag or touch swipes. It is completely inaccessible to keyboard users.
- Fix:
- Use real
<img>tags or addrole="img"witharia-labelto the divs. - Implement keyboard listeners (Left/Right arrows) to trigger the GSAP rotation tween.
- Respect
prefers-reduced-motionby reducing the entrance animation duration or disabling parallax.
- Use real
Browser Support
This snippet relies on GSAP (which handles vendor prefixing perfectly) and standard CSS 3D transforms.


