Glass Depth Stack
Three glass plates stacked like steps (×0.82, ×0.91, ×1) that slide under the pointer at three amplitudes, 8, 16 and 26 px: the back layers move less than the front one, depth reads in the gap — CSS, variables and 1.3 KB of JavaScript, frozen when the user reduces motion, solid when they reduce transparency.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Glassmorphism has 47 effects, including 6 free. Explore the category →
3 usage examples



How it works
A 16/10 scene (.gds-scene, 14 px corners, a midnight-blue radial gradient, perspective: 900px) holds three blurred pink, cyan and violet blobs, then the stack .gds-stack: min(70%, 320px) wide at 4/3, i.e. 320 × 240 px from a 458 px frame upwards. Inside, three .gds-layers overlap with inset: 0, 22 px corners, the same glass — a white gradient from 16% to 5%, a white rim at 22%, backdrop-filter: blur(var(--gds-blur, 6px)) saturate(150%). They differ by three variables: scale --s (0.82 / 0.91 / 1), opacity (55 / 75 / 100%) and amplitude --d (8 / 16 / 26 px). The text lives on the front layer.
The motion fits in one line: transform: translate3d(calc(var(--px, 0) * var(--d)), calc(var(--py, 0) * var(--d) + var(--lift, 0%)), 0) scale(var(--s, 1)), smoothed by transition: transform .12s ease-out. The script listens to pointermove on every .gds-scene of the page (several stacks possible), computes the pointer position from −0.5 to 0.5 and writes --px and --py between −1 and 1, reversed: pointer on the left, layers to the right. Measured 20 px from the corners in Chromium, Firefox and WebKit: ±7.6 / 15.2 / 24.7 px horizontally; when the pointer leaves, everything returns to 0. The layers do not come closer in depth (the Z component is 0): depth comes from scale and the amplitude gap, the front layer slides three times as much as the back one, like a foreground seen from a car.
The stack's settings aim at one thing: keeping all three layers visible. Shrunk to 82 and 91% and centered, the back layers would stay entirely hidden under the front layer at every pointer position (their edges always inside the front's, the 18 px amplitude gap never making up for the 29 px margin): the --lift variable (−22% for the back, −11% for the middle) raises them like steps. At rest the back sticks out by 31 px and the middle by 16 px; pointer top left, 47 and 25 px; bottom right, 15 and 7 px. .gds-stack does not declare transform-style: preserve-3d: in Firefox, a parent set that way whose child is transformed cancels its backdrop-filter (measured with real screen captures over a striped background), and since the layers stay at Z = 0 it would add nothing. The .gds-solid fallback is set by the script when the user reduces transparency or the browser ignores backdrop-filter; Safari before 18 reads the -webkit- form. The script walks through every .gds-scene: several stacks coexist, each with its own --px and --py; remove the gdsScene and gdsStack ids when you duplicate the block, the script does not use them. An older variant of this stack also exists in the category.
Accessibility
- Reduced motion: under
prefers-reduced-motion, the script ignores the pointer — measured in all three engines, layers still at both corners. The steps stay visible, depth reads without moving. - Reduced transparency and no blur: the script sets
.gds-solidon the scene whenprefers-reduced-transparency: reducematches or whenCSS.supportsknows neitherbackdrop-filternor its prefixed form. Measured under emulation in Chromium: solid layers (#1b2142,#222a52,#14162a), no more blur, desaturated blobs. Firefox 153 and WebKit 26 do not know this transparency setting yet: the fallback only applies there if blur is missing. - Contrast: the white text on the front layer holds thanks to its drop shadows (black
text-shadowat 40 and 55%). Without them, measured on the front layer: 2.62:1 median, 2.22:1 on the lightest area, when the pink blob passes underneath. Keep the shadows, or darken the front layer if the text gets longer. - Touch and keyboard: the parallax only responds to a hovering pointer. With a finger, swiping scrolls the page and the browser stops tracking; with a keyboard, nothing moves. No content depends on the motion: the three layers and their text stay readable at rest. If the back layers carry information, also give it a place in the front layer's text or in the page.
Browser compatibility
CSS (variables, calc(), translate3d, backdrop-filter) + 1.3 KB of JavaScript, no dependency. Measured in Chromium 151, Firefox 153 and WebKit 26: same amplitudes, same return to zero, same stop under reduced motion; the 6 px blur shows in all three, as the stack does not use preserve-3d (real screen capture for Firefox and WebKit, whose automated captures never show backdrop-filter). -webkit-backdrop-filter covers Safari before version 18. pointermove events exist since Chrome 55, Firefox 59 and Safari 13.
Without backdrop-filter (Firefox before 103 for instance), the script sets .gds-solid: three solid midnight-blue layers, white text, steps and parallax intact. Without JavaScript, the layers stay stepped at rest, with no parallax and no automatic fallback. With no hovering pointer (phone, tablet), the still steps are enough to read the three planes.
The code
HTML structure to paste into your page (the CSS + JS come with Premium):
<div class="demo-preview">
<div class="gds-scene">
<div class="gds-bg">
<span class="gds-orb gds-orb--1"></span> … (3 blobs)
</div>
<div class="gds-stack">
<div class="gds-layer gds-layer--back"></div>
<div class="gds-layer gds-layer--mid"></div>
<div class="gds-layer gds-layer--front">
<div><h3>…</h3><p>…</p></div>
</div>
</div>
</div>
</div>
The full HTML + CSS + JS of Glass Depth Stack, ready to paste — plus all 840 effects. Or $299 once, lifetime access.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Amplitudes (CSS — --d on .gds-layer--back, --mid, --front) |
8 / 16 / 26 px | The gap between layers creates depth: 4 / 12 / 30 px emphasizes it, 6 / 8 / 10 px flattens it. Beyond 40 px for the front, the layer leaves the scene at the corners. |
Steps (CSS — --lift) |
−22% (back), −11% (middle), as a percentage of the layer height | Layers stick out by 31 and 16 px at rest for a 240 px height. 0% hides them under the front again. For a sideways stack, add a horizontal offset in the first term of translate3d. |
Scale and opacity (CSS — --s, opacity) |
0.82 / 0.91 / 1 and 55 / 75 / 100% | Closer scales (0.94 / 0.97) give a deck of cards; wider ones (0.7 / 0.85), a tunnel. Too low an opacity makes the back layer invisible on a dark background. |
Blur per layer (CSS — --gds-blur) |
6px for all three layers | All three layers blur the same. For a blur that grows towards the front: --gds-blur: 3px on the back, 6px on the middle, 12px on the front; each layer also blurs the ones behind it. |
Layer tint (CSS — background of each .gds-layer--…) |
the same white gradient from 16% to 5% | One tint per layer helps name them: linear-gradient(135deg, rgba(255,120,110,.22), rgba(200,60,60,.08)) for a red layer. Keep opacity under 35% so the blur stays visible. |
Sensitivity (JS — var amp = 1) |
1: layers at full amplitude at the scene edge | 0.5 halves the motion. To follow the pointer instead of opposing it, remove the minus sign in front of nx and ny. |
Smoothness (CSS — transition: transform .12s ease-out) |
0.12 s | 0.3 s gives a stack that floats behind the pointer, 0 s a stack glued to the gesture. The same duration applies to the return to zero (pointerleave). |
FAQ
perspective: 900px, but the Z component of translate3d is 0: no layer moves towards the screen. Depth is a parallax illusion — smaller scale and slower motion for what is far —, the same as in a platform game. For real 3D, give each layer a Z (translate3d(…, …, -60px) for the back, -30px for the middle) and put transform-style: preserve-3d back on .gds-stack; beware, Firefox then loses all layer blur (measured), and Chromium computes each layer's blur from what lies behind it in the plane, not in 3D. Flat parallax is the compromise that works everywhere.deviceorientation, which needs permission on iOS) and write the same --px / --py between −1 and 1 — the CSS does not have to change.