Floating Card Field
A field of cards drifting at different depths in 3D space: pointer parallax varies by depth, scrolling pulls the whole field back and scatters it, and distant cards blur for depth of field.
This is a WebberUI Pro component
The install command below needs a licence to run; without one it returns 401.
Scatter work thumbnails, UI screenshots or any node across different depths as the backdrop for a landing page or portfolio. Each card's position, depth, rotation and size are derived deterministically from a seed and its index, so server and browser render the same layout; near cards are large and sharp, distant ones small and blurred. As the pointer moves, near cards shift more than far ones for parallax; scrolling away pulls the whole field back and scatters it outward from the center. A headline and buttons can sit in the middle — foreground content does not drift.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add "https://webberui.com/r/floating-card-field.en.json?t=<install token>"Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<FloatingCardField/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add "https://webberui.com/r/floating-card-field.en.json?t=<install token>"Or, once registries are configured in components.json, install it as @webberui/floating-card-field.
Usage
import {
FloatingCardField,
type FloatingCard,
} from "@/components/ui/floating-card-field";
const cards: FloatingCard[] = [
{ id: "a", src: "/works/dashboard.png" },
{ id: "b", src: "/works/album.png" },
{ id: "c", node: <div className="h-full w-full bg-gradient-to-br from-sky-400 to-indigo-500" /> },
// An explicit position and depth override the automatic layout
{ id: "d", src: "/works/player.png", x: 30, y: -20, z: 0.1 },
];
<FloatingCardField cards={cards} height="100vh" depthBlur={4} parallax={40}>
<h1 className="text-4xl font-semibold">Your work deserves to be seen</h1>
</FloatingCardField>;Props
| Prop | Type | Default | Description |
|---|---|---|---|
cards | FloatingCard[] | — | Card list |
count | number | — | Use only the first count cards; uses all when omitted |
seed | number | 1 | Layout seed; change it for a different arrangement |
depthBlur | number | 4 | Depth-of-field blur (px); a card blurs by depth × depthBlur |
parallax | number | 40 | Maximum pointer parallax offset (px), multiplied by (1 − depth) |
drift | number | 10 | Amplitude of each card's slow drift (px) |
scatter | number | 160 | Maximum outward scatter distance (px) while scrolling away |
cardWidth | number | 168 | Base card width (px), multiplied by a 0.8–1.2 size factor; aspect ratio 4:3 |
height | number | string | "100vh" | Container height; numbers are px |
container | RefObject<HTMLElement | null> | — | Pass when scrolling happens in a nested overflow container; defaults to the window |
children | ReactNode | — | Centered foreground content; does not drift |
className | string | — | Forwarded to the outermost container |
FloatingCard
| Field | Type | Default | Description |
|---|---|---|---|
id | string | — | React key; falls back to the index |
node | ReactNode | — | Card content; takes precedence over src |
src | string | — | URL of your own image |
alt | string | "" | Image alt text; the field is decorative, so usually empty |
x | number | hash | Horizontal percent from the center (−45 to 45) |
y | number | hash | Vertical percent from the center (−40 to 40) |
z | number | hash | Depth 0–1; 0 nearest, 1 farthest |
The FloatingCard and FloatingCardFieldProps types are also exported by name.
How it works
- Deterministic layout: each card uses
mulberry32seeded by seed and index to produce its position, depth, rotation (−8° to 8°), size (0.8–1.2) and drift rhythm — noMath.random, so server and browser render identically - Depth rendering: scale = 1 − depth × 0.5, opacity = 1 − depth × 0.35, blur = depth × depthBlur, and nearer cards stack on top. Depth is simulated with scale rather than
translateZbecause afilterblur flattenspreserve-3d - Pointer parallax: the pointer position relative to the container center maps to −1..1, is smoothed by a spring (stiffness 80, damping 20), then multiplied by (1 − depth) × parallax; disabled on touch devices
- Drift rhythm: each card floats on CSS keyframes with a period of
--wb-fcf-drift-period(default9s) times a 0.67–1.33 factor, staggered with a negative animation-delay. Override the variable to speed up or slow down the whole field - Scroll pull-back: scroll progress runs from the container top meeting the viewport top (0) to the container bottom leaving the viewport top (1); the field scales down to 0.7, each card moves scatter px outward from the center, and opacity drops another 30%
- Pausing: while the container is off-screen, drift animations pause and parallax stops updating
Accessibility
- The card field is decorative: the whole layer is
aria-hiddenand ignores pointer events; avoid putting focusable elements inside cards childrenlive in a separate foreground layer that is not hidden, so headlines and buttons remain readable and operable- When the user's system has "reduce motion" enabled, there is no drift, no pointer parallax and no scroll pull-back or scatter — cards rest at their initial positions; depth blur and scale are static depth cues and are kept
Halftone Background
Halftone background React component: a continuous halftone dot background whose density shifts with scroll progress and whose dots swell near the cursor, drawn with CSS gradients.
Silk Ribbons Background
A silk ribbon background drawn with Canvas 2D: several bezier ribbons sway slowly over a dark ground, with gradients across their width simulating a glossy sheen; no WebGL.