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 {
x: number;
@ -27,6 +32,7 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
let rotation = opts.initialRotation ?? 0;
let dragging = false;
const state = { dragging };
let offsetLocal: 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 springFactor = 0.2;
const maxAngularVelocity = 0.95;
const momentumDampening = 0.98;
const RESIZE_DEBOUNCE_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 ---
let lastMousePosition: Vec2 = { x: 0, y: 0 };
let activePointerId: number | null = null;
@ -48,9 +59,9 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
// --- 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
if (dragging) return;
if (state.dragging) return;
const rect = card.getBoundingClientRect();
const outside =
@ -70,7 +81,7 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
const down = (e: PointerEvent) => {
if (activePointerId !== null) return;
dragging = true;
state.dragging = true;
activePointerId = e.pointerId;
card.style.cursor = "grabbing";
card.setPointerCapture(e.pointerId);
@ -90,7 +101,7 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
};
const move = (e: PointerEvent) => {
if (!dragging || e.pointerId !== activePointerId) return;
if (!state.dragging || e.pointerId !== activePointerId) return;
const mx = e.pageX;
const my = e.pageY;
@ -135,7 +146,7 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
const up = (e: PointerEvent) => {
if (e.pointerId === activePointerId) {
dragging = false;
state.dragging = false;
activePointerId = null;
card.style.cursor = "grab";
// Momentum is handled in the render loop
@ -179,21 +190,36 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
// --- Render Loop ---
function render() {
if (!dragging) {
if (Math.abs(angularVelocity) > 0.01) {
if (!state.dragging) {
// --- Angular Momentum ---
if (Math.abs(angularVelocity) > 0.001) {
// Simple exponential damping
rotation += angularVelocity;
angularVelocity *= momentumDampening;
angularVelocity *= angularVelocityDecay;
} else angularVelocity = 0;
// --- Linear Momentum ---
const speed = Math.sqrt(
velocity.x * velocity.x + velocity.y * velocity.y,
);
if (speed > 0.01) {
center.x += velocity.x * 0.4;
center.y += velocity.y * 0.4;
velocity.x *= momentumDampening;
velocity.y *= momentumDampening;
// Calculate speed-dependent damping
// Clamp speed influence to avoid excessive damping
const speedInfluence =
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 };
}

36
src/util/index.ts

@ -50,20 +50,46 @@ export const getTimeout = () => {
return [timeout, clearTimers] as const;
};
export function debounce<T extends (...args: any[]) => void>(
func: T,
export function debounce<Fn extends (...args: any[]) => void>(
func: Fn,
wait: number,
): T {
): Fn {
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);
timeoutId = window.setTimeout(() => {
func.apply(this, args);
timeoutId = null;
}, 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) =>
text.length > length ? text.slice(0, length - 3) + "..." : text;

Loading…
Cancel
Save