Components / Depth of Field
Depth of Field
A stack of cards blurred by the equation a camera uses: c = A·f·|z − z_f| / (z_f·(z − f)). The (z − f) term is why real defocus is asymmetric — at the same distance either side of the focal plane the near side blurs several times harder, because it runs out of room toward the lens. A linear blur ramp is symmetric, which is exactly why it reads as fake. The pointer only chooses the focal plane, by where it is among the cards: well inside a card the plane is on that card, near an edge it sits between the two, so a card sharpens as the pointer nears it and softens as it leaves. The cards stand at equal steps of the focus ring, so every one of them, the front card included, changes by the same amount. Off the cards the plane eases back to rest. On a touch screen tap a card or drag across them, and tab to a card and focus racks to it.
Usage
import DepthOfField from '@/components/depth-of-field'
<DepthOfField aperture={2.8} focal={50}>
{cards}
</DepthOfField>Installation
npx shadcn@latest add https://morphiq.prathampatel.design/r/depth-of-field.jsonModel
c = A·f·|z − z_f| / (z_f·(z − f)), A = f/NN f-number · f focal length · z_f focal plane, set by where the pointer is among the cards
Props
| Property | Type | Default | Description |
|---|---|---|---|
aperture | number | 2.8 | f-number. Smaller is a shallower depth of field. |
focal | number | 50 | Focal length, mm. |
near | number | 200 | Depth of the frontmost card, the first child. Defaults to 200: much closer in, a card two steps of focus from the plane is already at the maxBlur ceiling, so most of the stack sits there and a card clears only as the plane arrives. |
far | number | 900 | Depth of the rearmost card, the last child. The cards between stand at even steps of the focus ring (even in 1/z, not in z), so every card is the same turn of focus from its neighbours and each sharpens and softens by the same amount. |
focus | number | 0.35 | Where the focal plane rests, 0..1 along the focus ring: 0 is the first card, 1 the last, 0.5 the middle one. Pointing among the cards and keyboard focus rack away from it; a pointer on nothing, or leaving the stack, eases back. |
followPointer | boolean | true | Focus by where the pointer is among the cards. Well inside a card the plane is on that card; within the blend distance of another card's visible part it sits between the two, half-way on their shared edge, so a card sharpens as the pointer nears it and softens as it leaves. Off every card the nearest one holds the plane for one blend distance and lets it go to rest over the next. A finger taps a card to rack there, or drags sideways and is followed like a pointer; focus stays on the card it lifts from, and a tap beside the cards sends it back to rest. Off, the plane only moves with focus. |
followKeyboard | boolean | true | Rack to a card while keyboard focus (:focus-visible) is inside it, or a key is pressed there. |
blend | number | 0.4 | The blend distance, as a fraction of how deep a pointer can get into the thinnest visible card: how far a neighbouring card's pull reaches into a card, and each way from an edge. Measured from the cards' own boxes where they rest, so it scales with the stack. 0 hands the plane over at the edge; 1 and above leave the thinnest card no place where it is in focus alone. |
maxBlur | number | 14 | Blur ceiling, px — a wildly defocused card stays a card. Blur is exact up to 70% of it and rolls off toward it above that. |
parallax | number | 0.04 | How much depth shifts a card with a mouse or pen, measured from the plane the pointer is asking for: a card on it holds still, nearer cards lean with the pointer and farther ones against it, each in one eased move. Applied through translate, so a card's own transform is kept. |
smoothing | number | 0.12 | How closely the pull follows where the pointer says the plane should be: the fraction of the gap one 60 fps frame closes, frame-rate independent. 1 is instant. |
children | ReactNode | — | The cards. Each direct child is one card, the first in front and the last at the back. |