Browse Source

use throttle instead of debounce for isOutside calculation

master
Muthu Kumar 1 month ago
parent
commit
818bbf08c6
Failed to extract signature
  1. 54
      src/draggable.attempts/6/Draggable.ts
  2. 36
      src/util/index.ts

54
src/draggable.attempts/6/Draggable.ts

@ -1,4 +1,9 @@
import { clamp, debounce, normaliseAngleDifference } from "../../util/index.ts"; import {
clamp,
debounce,
normaliseAngleDifference,
throttle,
} from "../../util/index.ts";
interface Vec2 { interface Vec2 {
x: number; x: number;
@ -27,6 +32,7 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
let rotation = opts.initialRotation ?? 0; let rotation = opts.initialRotation ?? 0;
let dragging = false; let dragging = false;
const state = { dragging };
let offsetLocal: Vec2 = { x: 0, y: 0 }; let offsetLocal: Vec2 = { x: 0, y: 0 };
let velocity: Vec2 = { x: 0, y: 0 }; let velocity: Vec2 = { x: 0, y: 0 };
@ -36,10 +42,15 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
const dampingFactor = 0.7; const dampingFactor = 0.7;
const springFactor = 0.2; const springFactor = 0.2;
const maxAngularVelocity = 0.95; const maxAngularVelocity = 0.95;
const momentumDampening = 0.98;
const RESIZE_DEBOUNCE_MS = 100; const RESIZE_DEBOUNCE_MS = 100;
const VIEWPORT_CHECK_INTERVAL_MS = 100; const VIEWPORT_CHECK_INTERVAL_MS = 100;
// Adjust damping factors (base + velocity-dependent part)
const baseDamping = 0.98; // Base exponential damping
const angularVelocityDecay = 0.99;
const velocityDecay = 0.005;
const maxEffectiveSpeed = 50;
// --- State --- // --- State ---
let lastMousePosition: Vec2 = { x: 0, y: 0 }; let lastMousePosition: Vec2 = { x: 0, y: 0 };
let activePointerId: number | null = null; let activePointerId: number | null = null;
@ -48,9 +59,9 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
// --- Helpers --- // --- Helpers ---
const checkViewportExit = debounce(() => { const checkViewportExit = throttle(() => {
// Don't check if we're dragging, user may still be able to move the card back into view // Don't check if we're dragging, user may still be able to move the card back into view
if (dragging) return; if (state.dragging) return;
const rect = card.getBoundingClientRect(); const rect = card.getBoundingClientRect();
const outside = const outside =
@ -70,7 +81,7 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
const down = (e: PointerEvent) => { const down = (e: PointerEvent) => {
if (activePointerId !== null) return; if (activePointerId !== null) return;
dragging = true; state.dragging = true;
activePointerId = e.pointerId; activePointerId = e.pointerId;
card.style.cursor = "grabbing"; card.style.cursor = "grabbing";
card.setPointerCapture(e.pointerId); card.setPointerCapture(e.pointerId);
@ -90,7 +101,7 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
}; };
const move = (e: PointerEvent) => { const move = (e: PointerEvent) => {
if (!dragging || e.pointerId !== activePointerId) return; if (!state.dragging || e.pointerId !== activePointerId) return;
const mx = e.pageX; const mx = e.pageX;
const my = e.pageY; const my = e.pageY;
@ -135,7 +146,7 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
const up = (e: PointerEvent) => { const up = (e: PointerEvent) => {
if (e.pointerId === activePointerId) { if (e.pointerId === activePointerId) {
dragging = false; state.dragging = false;
activePointerId = null; activePointerId = null;
card.style.cursor = "grab"; card.style.cursor = "grab";
// Momentum is handled in the render loop // Momentum is handled in the render loop
@ -179,21 +190,36 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
// --- Render Loop --- // --- Render Loop ---
function render() { function render() {
if (!dragging) { if (!state.dragging) {
if (Math.abs(angularVelocity) > 0.01) { // --- Angular Momentum ---
if (Math.abs(angularVelocity) > 0.001) {
// Simple exponential damping
rotation += angularVelocity; rotation += angularVelocity;
angularVelocity *= momentumDampening; angularVelocity *= angularVelocityDecay;
} else angularVelocity = 0; } else angularVelocity = 0;
// --- Linear Momentum ---
const speed = Math.sqrt( const speed = Math.sqrt(
velocity.x * velocity.x + velocity.y * velocity.y, velocity.x * velocity.x + velocity.y * velocity.y,
); );
if (speed > 0.01) { if (speed > 0.01) {
center.x += velocity.x * 0.4; // Calculate speed-dependent damping
center.y += velocity.y * 0.4; // Clamp speed influence to avoid excessive damping
velocity.x *= momentumDampening; const speedInfluence =
velocity.y *= momentumDampening; Math.min(speed, maxEffectiveSpeed) / maxEffectiveSpeed;
const currentDamping =
baseDamping * (1 - speedInfluence * velocityDecay);
// Ensure damping doesn't go below a minimum or above 1
const effectiveDamping = clamp(currentDamping, 0.8, 0.995); // Adjust min/max clamp
velocity.x *= effectiveDamping;
velocity.y *= effectiveDamping;
center.x += velocity.x;
center.y += velocity.y;
} else velocity = { x: 0, y: 0 }; } else velocity = { x: 0, y: 0 };
} }

36
src/util/index.ts

@ -50,20 +50,46 @@ export const getTimeout = () => {
return [timeout, clearTimers] as const; return [timeout, clearTimers] as const;
}; };
export function debounce<T extends (...args: any[]) => void>( export function debounce<Fn extends (...args: any[]) => void>(
func: T, func: Fn,
wait: number, wait: number,
): T { ): Fn {
let timeoutId: number | null = null; let timeoutId: number | null = null;
return function (this: ThisParameterType<T>, ...args: Parameters<T>) { return function (this: ThisParameterType<Fn>, ...args: Parameters<Fn>) {
if (timeoutId !== null) clearTimeout(timeoutId); if (timeoutId !== null) clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => { timeoutId = window.setTimeout(() => {
func.apply(this, args); func.apply(this, args);
timeoutId = null; timeoutId = null;
}, wait); }, wait);
} as T; } as Fn;
} }
export const throttle = <Fn extends (...args: any[]) => void>(
fn: Fn,
wait: number,
): Fn => {
let inThrottle = false;
let lastFn: ReturnType<typeof setTimeout> | undefined = undefined;
let lastTime = 0;
return function (this: ThisParameterType<Fn>, ...args: Parameters<Fn>) {
const context = this;
if (!inThrottle) {
fn.apply(context, args);
lastTime = Date.now();
inThrottle = true;
} else {
clearTimeout(lastFn);
lastFn = setTimeout(function () {
if (Date.now() - lastTime >= wait) {
fn.apply(context, args);
lastTime = Date.now();
}
}, Math.max(wait - (Date.now() - lastTime), 0));
}
} as Fn;
};
export const ellipses = (text: string, length: number = 100) => export const ellipses = (text: string, length: number = 100) =>
text.length > length ? text.slice(0, length - 3) + "..." : text; text.length > length ? text.slice(0, length - 3) + "..." : text;

Loading…
Cancel
Save