import { fastRaf } from './schedulers'; interface AnimationInstance { isCancelled: boolean; } let currentInstance: AnimationInstance | undefined; export function animateSingle(tick: Function, instance?: AnimationInstance) { if (!instance) { if (currentInstance && !currentInstance.isCancelled) { currentInstance.isCancelled = true; } instance = { isCancelled: false }; currentInstance = instance; } if (!instance!.isCancelled && tick()) { fastRaf(() => { animateSingle(tick, instance); }); } } export function animate(tick: Function) { if (tick()) { fastRaf(() => { animate(tick); }); } } export type TimingFn = (t: number) => number; export type AnimateNumberProps = { to: number | number[]; from: number | number[]; duration: number; onUpdate: (value: any) => void; timing?: TimingFn; onEnd?: () => void; }; export const timingFunctions = { linear: (t: number) => t, easeIn: (t: number) => t ** 1.675, easeOut: (t: number) => 1 - (1 - t ** 1.675), easeInOut: (t: number) => 0.5 * (Math.sin((t - 0.5) * Math.PI) + 1), easeInQuad: (t: number) => t * t, easeOutQuad: (t: number) => t * (2 - t), easeInOutQuad: (t: number) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t), easeInCubic: (t: number) => t * t * t, easeOutCubic: (t: number) => (--t) * t * t + 1, easeInOutCubic: (t: number) => (t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1), easeInQuart: (t: number) => t * t * t * t, easeOutQuart: (t: number) => 1 - (--t) * t * t * t, easeInOutQuart: (t: number) => (t < 0.5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t), easeInQuint: (t: number) => t * t * t * t * t, easeOutQuint: (t: number) => 1 + (--t) * t * t * t * t, easeInOutQuint: (t: number) => (t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t), }; export function animateNumber({ timing = timingFunctions.linear, onUpdate, duration, onEnd, from, to, }: AnimateNumberProps) { const t0 = Date.now(); let canceled = false; animate(() => { if (canceled) return false; const t1 = Date.now(); let t = (t1 - t0) / duration; if (t > 1) t = 1; const progress = timing(t); if (typeof from === 'number' && typeof to === 'number') { onUpdate(from + ((to - from) * progress)); } else if (Array.isArray(from) && Array.isArray(to)) { const result = from.map((f, i) => f + ((to[i] - f) * progress)); onUpdate(result); } if (t === 1 && onEnd) onEnd(); return t < 1; }); return () => { canceled = true; if (onEnd) onEnd(); }; }