← all topics

Tilt Card

CSS

A cursor-driven 3D tilt using CSS perspective + rotateX/rotateY, with a radial shine that tracks the cursor within the card. Rotation and shine are lerp-smoothed in a RAF loop so the card lags behind the cursor rather than snapping instantly.

LAUNCHShip fasterZero-config deployments with instant global rollback.
OBSERVEReal-time metricsEdge latency, request volume, and error rates at a glance.
Controls
18°
700px
0.14
0.12

concepts

cursor → rotation mapping

Mouse offset within the card is normalized to [-1, 1] by dividing by half the card's width/height, then multiplied by the max tilt angle in degrees.

lerp smoothing

Each RAF frame: current += (target - current) * speed. The card always moves a fixed fraction toward its target, creating an organic lag behind the cursor.

perspective — the viewer's eye distance

perspective(700px) tells the browser how far the viewer's eye is from the z=0 plane. Without it, rotateX/rotateY produce flat, isometric-looking transforms — there's no foreshortening so depth is invisible. Lower values (e.g. 300px) place the eye close to the surface, making tilts look dramatic and exaggerated. Higher values (e.g. 1400px) push the eye back, flattening the effect toward no perspective at all. Used inline inside transform, it applies to this element only — no parent .scene wrapper needed, though it can't be shared across siblings.

radial shine

A radial-gradient overlay is centered at the cursor's card-relative coordinates and fades to transparent. This fakes a specular highlight that tracks the light source.

core pattern

// mousemove — update targets, no DOM writes
const dx = (mouseX - cardCenterX) / halfWidth;  // [-1, 1]
const dy = (mouseY - cardCenterY) / halfHeight;
targetRotY =  dx * maxTilt;
targetRotX = -dy * maxTilt;

// RAF loop — lerp current values toward targets each frame
rotX += (targetRotX - rotX) * 0.12;
rotY += (targetRotY - rotY) * 0.12;
card.style.transform =
  `perspective(700px) rotateX(${rotX}deg) rotateY(${rotY}deg)`;