Respect Where the User Grabbed
MUST: Store the pointer's offset within the element at `pointerdown` and subtract it on every `pointermove` so the element stays glued to the spot the user grabbed. Never re-centre the element under the cursor on grab — it teleports on the first frame and the illusion of holding an object dies.
Store the pointer's offset within the element on pointerdown and subtract it on every move
const r = el.getBoundingClientRect();
const offset = { x: e.clientX - r.left, y: e.clientY - r.top };
// pointermove:
el.style.translate = `${e.clientX - offset.x}px ${e.clientY - offset.y}px`;Bad
Good
Why it matters
The cheapest way to write a drag is to set the element's position to the pointer's position, which silently centres it under the cursor. The result is a visible teleport at the instant of grab: you press the bottom edge of a card and the card jumps up so its middle is under your finger. Nothing in the physical world does this, and the illusion of holding an object dies in the first frame — before any of the release physics gets a chance to matter.
The fix is two lines and no library: at pointerdown, record `e.clientY - element.getBoundingClientRect().top` (and the same for x), then on every pointermove subtract that offset from the pointer position. The element stays glued to the exact spot you took hold of. This is grab-time positioning, and no other rule in the corpus covers it: "Clean Drag Interactions" is about text selection and inert during the drag, and "Give Drag Gestures Real Physics" is about what happens on release. Between them sits the very first frame, which is the one users feel most sharply.