React
Tilt a Card with the Pointer and No Library
Map the pointer position to rotateX and rotateY, cap the angle, and reset when the pointer leaves.
- React
- CSS
- UI
- Motion
On this page
A tilt library is a large dependency for two angles. The pointer already tells you where it is inside an element. Turn that position into rotateX and rotateY, put perspective on the parent, and transition the transform so the card eases back when the pointer leaves. The login on the glass demo uses this and nothing else for motion.
Map the pointer into a small range
Divide the pointer offset by the element size so you get 0 at the left or top and 1 at the right or bottom. Subtract 0.5 so the center is zero. Multiply by a maximum angle. Vertical motion drives rotateX, and it is inverted: a pointer above the center should tip the top of the card away, which is a positive rotateX in the usual screen orientation. Horizontal motion drives rotateY.
const px = (event.clientX - rect.left) / rect.width;
const py = (event.clientY - rect.top) / rect.height;
const maxX = 12;
const maxY = 16;
const rotateX = (0.5 - py) * maxX;
const rotateY = (px - 0.5) * maxY;
Perspective lives on the parent
rotateX on an element with no perspective ancestor looks like a squash. Set perspective on the wrapper you measure, around 1000 to 1400 pixels. Preserve 3D on the tilted element if it has children at different Z depths. Transition transform at about 200ms so a fast pointer does not stutter, and so the return to rest is visible.
.stage {
perspective: 1200px;
}
.card {
transform-style: preserve-3d;
transition: transform 200ms ease-out;
}
Return to rest
onPointerLeave sets both angles to zero. Also listen for pointer cancel, which fires when the browser takes the gesture back. Leaving the angles at the last position makes the card look stuck, and a stuck card looks broken on a phone where the finger has left.
Honor reduced motion
Read prefers-reduced-motion before you set state. When it matches, return immediately. The card stays at rest, the form still works, and you avoid a transition on every move for someone who asked for less motion. Do this check in the handler so a system setting change applies without a reload.
- Measure the stage, not the card, if the card is what moves. Measuring the moving box makes the angle chase itself.
- Cap the angle. Past 15 degrees, labels distort and hit targets slide under the cursor.
- Keep tilt off until hover or a pointer move. A card that idles in a spin is a decoration, not a control.
- Touch screens can skip tilt. A finger is there to tap the button, not to orbit the form.
If the tilted object is a form, test the submit button at the corner of the card. The hit target has to stay under the pointer. When it does not, the angle is too large or the perspective is too short. The numbers in the glass login, 12 and 16, were chosen because the Continue button remains easy to press at the edge.
