feat: inertia
This commit is contained in:
@@ -38,19 +38,74 @@ export default function FloatingWindow({
|
||||
const ref = containerRef ?? internalRef;
|
||||
const dragRef = useRef<{ startX: number; startY: number; origX: number; origY: number } | null>(null);
|
||||
const resizeRef = useRef<{ startX: number; startY: number; origW: number; origH: number } | null>(null);
|
||||
const velocityRef = useRef({ vx: 0, vy: 0, lastX: 0, lastY: 0, lastT: 0 });
|
||||
const inertiaRef = useRef(0);
|
||||
const posRef = useRef({ x, y });
|
||||
posRef.current = { x, y };
|
||||
|
||||
useEffect(() => { if (focused) ref.current?.focus(); }, [focused]);
|
||||
|
||||
// Cancel any running inertia animation
|
||||
const stopInertia = useCallback(() => {
|
||||
if (inertiaRef.current) { cancelAnimationFrame(inertiaRef.current); inertiaRef.current = 0; }
|
||||
}, []);
|
||||
|
||||
const onDragStart = useCallback((e: React.MouseEvent) => {
|
||||
if ((e.target as HTMLElement).closest("button")) return;
|
||||
e.preventDefault(); onFocus(id);
|
||||
ref.current?.focus();
|
||||
stopInertia();
|
||||
dragRef.current = { startX: e.clientX, startY: e.clientY, origX: x, origY: y };
|
||||
velocityRef.current = { vx: 0, vy: 0, lastX: e.clientX, lastY: e.clientY, lastT: performance.now() };
|
||||
document.documentElement.classList.add("cursor-grabbing");
|
||||
const onMove = (ev: MouseEvent) => { if (!dragRef.current) return; onUpdate(id, { x: dragRef.current.origX + (ev.clientX - dragRef.current.startX), y: Math.max(0, dragRef.current.origY + (ev.clientY - dragRef.current.startY)) }); };
|
||||
const onUp = () => { dragRef.current = null; document.documentElement.classList.remove("cursor-grabbing"); document.removeEventListener("mousemove", onMove); document.removeEventListener("mouseup", onUp); };
|
||||
document.addEventListener("mousemove", onMove); document.addEventListener("mouseup", onUp);
|
||||
}, [id, x, y, onUpdate, onFocus]);
|
||||
|
||||
const onMove = (ev: MouseEvent) => {
|
||||
if (!dragRef.current) return;
|
||||
const now = performance.now();
|
||||
const dt = now - velocityRef.current.lastT;
|
||||
if (dt > 0) {
|
||||
const smooth = 0.3;
|
||||
const rawVx = (ev.clientX - velocityRef.current.lastX) / dt * 16;
|
||||
const rawVy = (ev.clientY - velocityRef.current.lastY) / dt * 16;
|
||||
velocityRef.current.vx = velocityRef.current.vx * (1 - smooth) + rawVx * smooth;
|
||||
velocityRef.current.vy = velocityRef.current.vy * (1 - smooth) + rawVy * smooth;
|
||||
velocityRef.current.lastX = ev.clientX;
|
||||
velocityRef.current.lastY = ev.clientY;
|
||||
velocityRef.current.lastT = now;
|
||||
}
|
||||
onUpdate(id, {
|
||||
x: dragRef.current.origX + (ev.clientX - dragRef.current.startX),
|
||||
y: Math.max(0, dragRef.current.origY + (ev.clientY - dragRef.current.startY)),
|
||||
});
|
||||
};
|
||||
|
||||
const onUp = () => {
|
||||
const { vx, vy } = velocityRef.current;
|
||||
dragRef.current = null;
|
||||
document.documentElement.classList.remove("cursor-grabbing");
|
||||
document.removeEventListener("mousemove", onMove);
|
||||
document.removeEventListener("mouseup", onUp);
|
||||
|
||||
// Kick off inertia if there's meaningful velocity
|
||||
if (Math.abs(vx) > 0.5 || Math.abs(vy) > 0.5) {
|
||||
let curVx = vx;
|
||||
let curVy = vy;
|
||||
const friction = 0.92;
|
||||
const tick = () => {
|
||||
curVx *= friction;
|
||||
curVy *= friction;
|
||||
if (Math.abs(curVx) < 0.3 && Math.abs(curVy) < 0.3) { inertiaRef.current = 0; return; }
|
||||
posRef.current = { x: posRef.current.x + curVx, y: Math.max(0, posRef.current.y + curVy) };
|
||||
onUpdate(id, posRef.current);
|
||||
inertiaRef.current = requestAnimationFrame(tick);
|
||||
};
|
||||
inertiaRef.current = requestAnimationFrame(tick);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousemove", onMove);
|
||||
document.addEventListener("mouseup", onUp);
|
||||
}, [id, x, y, onUpdate, onFocus, stopInertia]);
|
||||
|
||||
const onResizeStart = useCallback((e: React.MouseEvent) => {
|
||||
e.preventDefault(); e.stopPropagation(); onFocus(id);
|
||||
|
||||
Reference in New Issue
Block a user