Layered Parallax 3D
Three translucent indigo, violet and magenta plates stacked at 0, 30 and 60 px of depth, with a pink "3D" badge even further forward: the whole stack tilts toward the pointer up to 10° and the layers shift over one another like a cross-section you turn in your hand — a finger or the arrow keys work too, and it freezes face-on for anyone who chose to limit animation.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. 3D has 50 effects, including 8 free. Explore the category →
3 usage examples



How it works
In the middle of .fx-0007-root, a near-black area 300 px tall or more, the .demo-3d frame (280 × 200 px) hosts the stack. .parallax-container (200 × 140 px) holds the perspective (perspective: 1000px) and listens to the pointer; its child .lp3d-scene is set to transform-style: preserve-3d and holds the layers. The back layer is indigo (rgba(99, 102, 241, .3)) at 0 px; the second one violet (rgba(139, 92, 246, .4)), pushed 30 px forward and scaled to 90%; the third one magenta (rgba(217, 70, 239, .5)) at 60 px and 80%. The badge .lp3d-layer-4, 60 × 60 px, #f43f5e → #ec4899 gradient, sits at 90 px; with inset: auto it stays in its natural place, in the top left corner of the stack.
Every stack on the page has its own instance of the script. When the pointer crosses .parallax-container, its position (0 to 1) rotates .lp3d-scene (rotateX, rotateY): the side under the pointer comes toward you, up to 10° at the edges (9.1° and 8.9° read at 5% from the corner, alike in Chromium 151 and Firefox 153). Since the layers sit at different depths, they do not turn around the same plane: with the pointer at the right edge, the magenta layer slides 10.9 px against the back layer and the badge 15.3 px; with the pointer at the top, the badge drops 17 px. That shift, not the tilt alone, is what reads as thickness. The turn is damped over 0.1 s; as soon as the pointer goes away, the stack swings back face-on.
With a finger or a pen the stack turns the same way (pointer events); touch-action: none stops the page from scrolling during the gesture, and the stack straightens when the finger lifts. From the keyboard, the area takes focus (tabindex="0", named by role="group" and aria-label): each arrow nudges by a tenth the point the stack turns toward, and Escape or tabbing on brings it face-on. The angle is computed as a fraction of the area, not in pixels: a stack enlarged to 400 × 220 px, as in the scenes, still tilts 10° at most. Watch out: on .lp3d-scene, clipped overflow, a filter or any opacity under 1 flattens the 3D and kills the shift between layers.
Accessibility
- Reduced motion: if the system asks for less animation (
prefers-reduced-motion: reduce), a rule of its own under.fx-0007-rootcancels the rotation of.lp3d-sceneand its transition. The stack stays face-on and still; its layers read as three nested frames with the badge in the corner. The three engines tested (Chromium, Firefox, WebKit) give 0° under the mouse, against 9° normally. - Keyboard: tabbing lands on the stack, marked by a 2 px pink
#f9a8d4ring held 6 px off (:focus-visible, 10.9:1 on the background). → ×4 then ↑ ×2 turn it by 8.0° and 4.0°; Escape, or moving on to the next element, straightens it. - Screen readers: the stack is announced as a group named "Layered stack: the arrow keys tilt it" (
aria-label). The empty layers are not read, the badge is ("3D"). If your layers carry meaning (materials, steps), write it as text in the layers or next to them, like the captions of the road and glazing scenes, and translate thearia-label. - Contrast: "3D" in 24 px bold white on the badge: 3.7:1 on
#f43f5eand 3.5:1 on#ec4899, enough for large text (3:1 threshold). For smaller text in the badge, darken its gradient.
Browser compatibility
CSS (perspective, transform-style: preserve-3d, translateZ, touch-action, :focus-visible) + JS with pointer events, no dependencies. Measured in Chromium 151 and Firefox 153: same tilt, same shifts between layers (10.9 px for the magenta layer at the right edge), two independent copies on one page, face-on stack under reduced motion, finger rotation (Chromium in touch mode). Playwright's WebKit (26.5) flattens all nested 3D, even on a reference cube: its captures show the stack with no shift between layers, which says nothing about the real Safari, not checked.
Without preserve-3d (or if a parent flattens it), the stack turns as one block and the layers no longer shift: you keep three nested frames that tilt. Without :focus-visible, the browser's default focus ring shows instead.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0007-root">
<div class="demo-3d">
<div class="parallax-container" tabindex="0" role="group" aria-label="…">
<div class="lp3d-scene">
<div class="lp3d-layer lp3d-layer-1"></div>
<div class="lp3d-layer lp3d-layer-2"></div>
<div class="lp3d-layer lp3d-layer-3"></div>
<div class="lp3d-layer lp3d-layer-4">3D</div>
</div>
</div>
</div>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Maximum tilt (JS — MAX_TILT = 10) |
10° | Angle reached at the edges, whatever the stack size. At 6 the layers barely shift; at 15 the shift is much more pronounced. |
Layer depth (CSS — translateZ of .lp3d-layer-2, -3, -4) |
30, 60 and 90 px | The shift between layers is proportional to this gap: double all three values (60, 120, 180) and the magenta layer slides about twice as far at the same angle. |
Layer size (CSS — scale(.9) and scale(.8)) |
90% and 80% | Without scaling, the layers have the same size and overlap like panes; with different heights (inset), they form bands, as in the road scene. |
Colors (CSS — background of .lp3d-layer-1 to -4) |
rgba(99, 102, 241, .3), rgba(139, 92, 246, .4), rgba(217, 70, 239, .5), #f43f5e → #ec4899 | Keep the back and middle layers transparent: that transparency is what reveals the stack. |
Badge (CSS — .lp3d-layer-4) |
60 × 60 px, top left corner | width: auto and a padding for a label; top and right to place it elsewhere; inset: 0; margin: auto to center it. |
Area size (CSS — .parallax-container, .demo-3d) |
200 × 140 px in a 280 × 200 px frame | Enlarge .demo-3d as well, or remove its dimensions. The angle does not change with the size. |
Perspective (CSS — perspective: 1000px on .parallax-container) |
1000px | 600px stresses the depth (front layers clearly larger); 2000px flattens the whole thing. |
FAQ
inset: auto, the badge keeps the natural place of a positioned element: the top left corner of the stack, which makes it stick out like a label. To center it, give it inset: 0; margin: auto; to put it top right, top: -16px; right: -14px, as in the scenes..lp3d-scene make the browser flatten its content: overflow: hidden, filter, and an opacity under 1. The stack then turns as one block. Put those properties on the root or on the layers themselves, never on .lp3d-scene. Also check that .parallax-container keeps its perspective.