Floating Elements 3D
Three 60 px square tiles (indigo, magenta, cyan-green) in a row, rising and sinking 20 px while tilting in 3D on a 6 s loop, offset by 2 s from one tile to the next: they never move together, like objects floating on water. Pure CSS, zero JavaScript, held at different heights when the user reduces motion.
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
The .fx-0017-root root (#0a0a0f background, at least 300 px tall) centers a .floating-3d row (flex, 20 px gap) that carries a single camera for the three tiles: perspective: 800px. Each .floating-element is 60 × 60 px with 0.75 rem rounded corners and its own 135° gradient: indigo → violet, magenta → pink, cyan → green. All three play the same 6 s float3d animation, ease-in-out, forever.
The loop has four steps: at the start the tile is flat; at 25% it rises 20 px and tilts 10° on both axes; at 50% it is back at its height, turned 20° around the vertical axis; at 75% it sinks 20 px, tipped −10° forward; then it returns to rest. The offset comes from negative animation-delay values: 0 s, −2 s and −4 s. A negative delay starts the animation already under way, so from the very first paint the three tiles are a third of a loop apart, with no waiting and no synchronized start. Since the camera is shared and sits at the center of the row, the left tile is seen slightly from the right and the right tile slightly from the left: the same tilt reads differently on each of the three.
Measured in Chromium 151 and Firefox 153: about 3% of the block's pixels change every 0.8 s, two independent copies, 0 errors. Only transform is animated. Here each tile turns on its own under its parent's perspective, with no nested volume: even the test WebKit (Playwright 26.5), which flattens cubes and rings, renders the same tilts (0.5% difference from Chromium on the still frame). The real Safari was not verified.
Accessibility
- Reduced motion: under
prefers-reduced-motion: reduce, a block scoped to.fx-0017-rootstops the three loops and sets each tile at a different step of the loop: the first 10 px higher and tilted, the second turned 20°, the third 10 px lower and tipped. The idea of floating objects remains, without motion. Measured in Chromium and Firefox: 0 running animations, 0% of pixels changing over 0.8 s (3% without the preference). - Continuous motion: the loop never stops; next to text people need to read, provide a way to pause it (WCAG 2.2.2), for instance
animation-play-state: pausedon a button or when hovering the row. - Contrast: the white digits reach 4.5:1 on indigo but 2.4 to 2.5:1 on cyan-green, below the 4.5:1 threshold for 16 px text. To put information there, darken the gradient or enlarge the text (24 px bold, 3:1 threshold).
- No interaction: the tiles react to neither mouse, finger nor keyboard; if they carry useful figures, as in the first example, those are read as ordinary text.
Browser compatibility
CSS (3D transform under perspective, negative animation-delay, @keyframes), zero JavaScript, no dependencies. Measured in Chromium 151 and Firefox 153: same floating (0.01% difference on the still frame), two independent copies, 0 errors. WebKit 26.5 (Playwright's test engine): same tilts; the real Safari was not verified.
Without 3D transforms, the tiles rise and sink flat, with no tilt; without animation, they stay aligned and still. Their content stays readable either way.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0017-root">
<div class="demo-3d">
<div class="floating-3d">
<div class="floating-element">1</div>
<div class="floating-element">2</div>
<div class="floating-element">3</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 |
|---|---|---|
Amplitude (CSS — @keyframes float3d) |
±20 px, tilts of 10 to 20° | ±8 px and 5° for barely visible floating; ±30 px for lighter objects. Keep the 0% and 100% steps identical for a seamless loop. |
Duration (CSS — 6s in the animation of .floating-element) |
6 s | 8 to 10 s for a calm page. If you change it, keep the delays at one third and two thirds of the duration (−2.67 s and −5.33 s for 8 s). |
Offset (CSS — animation-delay of :nth-child(2) and (3)) |
0 s, −2 s, −4 s | Delays of 0 give three synchronized tiles, like a single piece; irregular delays (−1.3 s, −4.1 s) look more natural. Negative values avoid a synchronized start on load. |
Size and gap (CSS — width / height of .floating-element, gap of .floating-3d) |
60 px, 20 px gap | Between 72 and 90 px, a figure and its unit fit in the tile. Leave at least 20 px of gap: the tiles turn 20° and spill over a little on the sides. |
Perspective (CSS — perspective of .floating-3d) |
800px | 400 px makes the tilt very visible; 1,500 px makes it almost flat. The camera sits at the center of the row: the closer it is, the more the edge tiles look seen from an angle. |
| Number of tiles (HTML and CSS) | 3 | For 4 tiles, add an element and a :nth-child(4) rule; spread the delays in quarter loops (0, −1.5 s, −3 s, −4.5 s for 6 s). |
FAQ
animation-delay would make tiles 2 and 3 wait 2 and 4 s, still and aligned, before moving. A negative delay starts the animation as if it had already been running for 2 or 4 s: from the first paint, the three tiles are at different moments of the loop..floating-3d:hover .floating-element, .floating-3d:focus-within .floating-element { animation-play-state: paused }) or keep the link beside it, like the booking button in the first example.