getLocalOffset
A utility function that computes the CSS offset delta needed to position an element at specific viewport coordinates, accounting for arbitrary ancestor transforms (scale, rotation, skew).
Usage
import { getLocalOffset } from 'dragdoll';
const element = document.querySelector('.draggable') as HTMLElement;
// Where the element should end up (viewport coordinates).
const targetX = 200;
const targetY = 300;
// Compute how much to adjust the element's CSS offset.
const delta = getLocalOffset(element, targetX, targetY);
// Apply the delta (e.g. to the translate CSS property).
element.style.translate = `${delta.x}px ${delta.y}px`;Signature
function getLocalOffset(
element: HTMLElement,
targetX: number,
targetY: number,
result?: { x: number; y: number },
): { x: number; y: number };Parameters
element
The element to position. The function measures the element's current viewport position and computes the delta relative to its parent's coordinate system.
targetX
The desired viewport x coordinate (getBoundingClientRect().left) for the element.
targetY
The desired viewport y coordinate (getBoundingClientRect().top) for the element.
result
An optional object to write the result into. If provided, the object's x and y properties are updated in place and the same object is returned. This avoids allocating a new object on every call, which is useful in performance-sensitive code paths.
- Optional.
- Default is
{ x: 0, y: 0 }(a new object).
Return Value
An object { x, y } representing the delta to add to the element's current CSS offset (left/top, translate, or transform: translate()). After applying this delta, the element's getBoundingClientRect().left will equal targetX and .top will equal targetY.
How It Works
When an element has ancestor transforms (e.g. a parent with scale(0.5) rotate(45deg)), a 1px change in the element's CSS left or translate does not produce a 1px change in viewport position. The relationship between CSS offsets and viewport position is defined by the parent's world transform matrix.
getLocalOffset extracts the 2x2 linear subpart of the parent's world transform matrix (the Jacobian), inverts it, and multiplies the viewport delta to produce the correct CSS offset delta. This analytical approach requires only 1 forced reflow (for getBoundingClientRect) compared to the 3 reflows a probing approach would need.
If the parent's transform matrix is degenerate (e.g. scale(0)), the function falls back to a simple viewport delta.
Common Use Cases
Drag preview element alignment
After dragging a drag preview element (clone), transfer its final viewport position back to the original element. The original may have transformed ancestors, so a simple viewport delta would be incorrect.
// In onEnd callback:
const offset = getLocalOffset(originalElement, previewRect.x, previewRect.y);
const parts = (getComputedStyle(originalElement).translate || '').split(' ');
const x = (parseFloat(parts[0]) || 0) + offset.x;
const y = (parseFloat(parts[1]) || 0) + offset.y;
originalElement.style.translate = `${x}px ${y}px`;See the Drag Preview example for a complete implementation.
Container reparenting with animation
After reparenting an element to a new container on drag end, compute the offset to hold the element at its drag-end viewport position, then animate it to its natural position.
// Record viewport position, clear drag transform, reparent.
const rect = element.getBoundingClientRect();
element.style.transform = '';
targetContainer.appendChild(element);
// Compute offset to maintain viewport position.
const delta = getLocalOffset(element, rect.x, rect.y);
// Animate from drag-end position to natural position.
element.style.transform = `translate(${delta.x}px, ${delta.y}px)`;
element.clientHeight; // Force reflow to commit starting transform.
element.classList.add('animate');
element.style.transform = 'matrix(1, 0, 0, 1, 0, 0)';See the Advanced Collision Detector example for a complete implementation.
React drag preview end positioning
After a drag preview ends, transfer the final position back to the original elements using getLocalOffset.
onEnd: (drag) => {
const el = elementRef.current;
const item = drag.items[0];
if (!el || !item) return;
const offset = getLocalOffset(el, item.clientRect.x, item.clientRect.y);
const parts = (getComputedStyle(el).translate || '').split(' ');
const x = (parseFloat(parts[0]) || 0) + offset.x;
const y = (parseFloat(parts[1]) || 0) + offset.y;
el.style.translate = `${x}px ${y}px`;
},See the React Drag Preview examples for complete implementations.
Exports
import { getLocalOffset } from 'dragdoll';