3D Glass Tilt Card
- Skia
- Reanimated
- Gesture Handler
- Haptics
A card that turns toward your finger, with its printing floating at different depths and a highlight that behaves as though the light stays where it is. Most of the work is in placing the text, not in the tilt.
Where the text really sits
Font metrics describe the face, not the word you typed, so aligning to ascent and descent leaves visible gaps. This asks the font which glyphs intersect a horizontal band and bisects twelve times to find where ink actually begins and ends. The card's rows are then anchored to real ink, and they stay put when the label changes.
for (let i = 0; i < 12; i++) { const topMid = (topLow + topHigh) / 2; if (font.getGlyphIntercepts(glyphs, positions, ascent, topMid).length) topHigh = topMid; else topLow = topMid; // …}return { top: topLow, bottom: bottomHigh, descent: Math.max(0, descent) };Depth is one number per layer
Each printed element carries a depth: the number 0.85, the chip 0.6, the footer 0.4. That number times the pointer offset times a 26px span gives how far the layer slides, and the sign is inverted so layers move against the tilt. One constant per layer is the whole parallax model, and the ordering of the depths is what sells the glass.
export const PARALLAX_SPAN = 26;export const CHIP_DEPTH = 0.6;export const NUMBER_DEPTH = 0.85;export const FOOTER_DEPTH = 0.4;// …const k = depth * amount * span;return { dx: -nx * k, dy: -ny * k };The highlight stays with the room
If the sheen were painted on the card it would turn with it, which reads as a sticker. Instead its centre moves opposite the tilt at 0.55 of the pointer offset, from a rest point up and left of centre, then clamps at the card edges. The light appears to stay where it is while the card moves under it.
const uRaw = 0.32 - nx * 0.55;const vRaw = 0.30 - ny * 0.55;const u = uRaw < 0 ? 0 : uRaw > 1 ? 1 : uRaw;const v = vRaw < 0 ? 0 : vRaw > 1 ? 1 : vRaw;return { u, v };Tuning
- 16
- #FFFFFF
- 0.5
- On
<GlassCardTilt params={{ tiltMax: 16, sheenColor: "#FFFFFF", parallax: 0.5, refractEdge: true }} fireSignal={0} />