import { clamp } from "~/shared/general.utils"; export const linear = (progress: number): number => progress; export const ease = (progress: number): number => -(Math.cos(Math.PI * progress) - 1) / 2; export function useTween(ref: Ref, animation: (progress: number) => number, then: () => void) { // State variables for animation let initial = ref.value; let current = ref.value; let end = ref.value; let animationFrame: number; let stop = true; let last = 0; // Velocity tracking with sign preservation let velocity = 0; const velocityDecay = 0.92; // Slightly faster decay for more responsive feel function loop(t: DOMHighResTimeStamp) { // Cap the elapsed time to prevent jumps during lag or tab switches const elapsed = Math.min(t - last, 32); last = t; // Update velocity considering the sign of the movement velocity = velocity * velocityDecay + (end - current) * (1 - velocityDecay); // Apply velocity to current position // Normalize by elapsed time to maintain consistent speed regardless of frame rate current = clamp(current + velocity * (elapsed / 16), initial, end); if(current === end) stop = true; // Trigger callback then(); // Continue animation if not stopped if (!stop) { animationFrame = requestAnimationFrame(loop); } } return { stop: () => { cancelAnimationFrame(animationFrame); stop = true; velocity = 0; // Reset velocity when stopping }, update: (target: number, duration: number) => { if (stop) { // Only reset initial position when starting a new animation initial = current; last = performance.now(); } end = target; if (stop) { stop = false; loop(performance.now()); } }, refresh: () => { current = ref.value; velocity = 0; // Reset velocity on refresh }, current: () => current, }; }