112 lines
3.0 KiB
TypeScript
112 lines
3.0 KiB
TypeScript
import type { Scheduler } from './schedulers';
|
|
import { requestMeasure } from '../lib/fasterdom/fasterdom';
|
|
|
|
interface AnimationInstance {
|
|
isCancelled: boolean;
|
|
}
|
|
|
|
let currentInstance: AnimationInstance | undefined;
|
|
|
|
export function animateSingle(tick: Function, schedulerFn: Scheduler, instance?: AnimationInstance) {
|
|
if (!instance) {
|
|
if (currentInstance && !currentInstance.isCancelled) {
|
|
currentInstance.isCancelled = true;
|
|
}
|
|
|
|
instance = { isCancelled: false };
|
|
currentInstance = instance;
|
|
}
|
|
|
|
if (!instance!.isCancelled && tick()) {
|
|
schedulerFn(() => {
|
|
animateSingle(tick, schedulerFn, instance);
|
|
});
|
|
}
|
|
}
|
|
|
|
export function cancelSingleAnimation() {
|
|
const dumbScheduler = (cb: AnyFunction) => cb;
|
|
const dumbCb = () => undefined;
|
|
|
|
animateSingle(dumbCb, dumbScheduler);
|
|
}
|
|
|
|
export function animate(tick: Function, schedulerFn: Scheduler) {
|
|
schedulerFn(() => {
|
|
if (tick()) {
|
|
animate(tick, schedulerFn);
|
|
}
|
|
});
|
|
}
|
|
|
|
export function animateInstantly(tick: Function, schedulerFn: Scheduler) {
|
|
if (tick()) {
|
|
schedulerFn(() => {
|
|
animateInstantly(tick, schedulerFn);
|
|
});
|
|
}
|
|
}
|
|
|
|
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 * 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 ** 3,
|
|
easeOutCubic: (t: number) => (--t) * t * t + 1,
|
|
easeInOutCubic: (t: number) => (t < 0.5 ? 4 * t ** 3 : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1),
|
|
easeInQuart: (t: number) => t ** 4,
|
|
easeOutQuart: (t: number) => 1 - (--t) * t ** 3,
|
|
easeInOutQuart: (t: number) => (t < 0.5 ? 8 * t ** 4 : 1 - 8 * (--t) * t ** 3),
|
|
easeInQuint: (t: number) => t ** 5,
|
|
easeOutQuint: (t: number) => 1 + (--t) * t ** 4,
|
|
easeInOutQuint: (t: number) => (t < 0.5 ? 16 * t ** 5 : 1 + 16 * (--t) * t ** 4),
|
|
};
|
|
|
|
export function animateNumber({
|
|
timing = timingFunctions.linear,
|
|
onUpdate,
|
|
duration,
|
|
onEnd,
|
|
from,
|
|
to,
|
|
}: AnimateNumberProps) {
|
|
const t0 = Date.now();
|
|
let canceled = false;
|
|
|
|
animateInstantly(() => {
|
|
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;
|
|
}, requestMeasure);
|
|
|
|
return () => {
|
|
canceled = true;
|
|
if (onEnd) onEnd();
|
|
};
|
|
}
|