Skip to content

Instantly share code, notes, and snippets.

@jamesliu96
Created September 12, 2024 08:32
Show Gist options
  • Save jamesliu96/c9af95c1488ef92baac53e81fa6ff19d to your computer and use it in GitHub Desktop.
Save jamesliu96/c9af95c1488ef92baac53e81fa6ff19d to your computer and use it in GitHub Desktop.
import { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from 'react';
// eslint-disable-next-line @typescript-eslint/ban-types
const isFunc = (x: unknown): x is Function => typeof x === 'function';
const getState = <T>(s: T, v?: SetStateAction<T>): T | undefined => (isFunc(v) ? v(s) : v);
function useStateRealtime<T>(init: T | (() => T)): [T, Dispatch<SetStateAction<T>>, () => T];
function useStateRealtime<T = undefined>(): [
T | undefined,
Dispatch<SetStateAction<T | undefined>>,
() => T | undefined
];
function useStateRealtime<T>(
init?: T | (() => T)
): [T | undefined, Dispatch<SetStateAction<T | undefined>>, () => T | undefined] {
const state = getState(undefined, init);
const [v, setV] = useState(state);
const vRef = useRef(state);
return [
v,
useCallback((newVal?: SetStateAction<T | undefined>) => {
const newState = getState(vRef.current, newVal);
vRef.current = newState;
setV(newState);
}, []),
useCallback(() => vRef.current, [])
];
}
export enum AnimationMode {
ConstantSpeed,
ConstantTime
}
export const useAnimation = (
value: number,
speedOrTime: number,
mode = AnimationMode.ConstantSpeed,
setValue?: Dispatch<SetStateAction<number>>,
easeFn?: (x: number) => number,
sync = false,
delay = 0
// eslint-disable-next-line max-params
): [number, boolean, Dispatch<SetStateAction<number>>] => {
const [v, setV, getV] = useStateRealtime(() => value);
const [running, setRunning] = useState(false);
const rafRef = useRef<number>();
useEffect(() => {
const t0 = Date.now() + delay;
const vDelta = value - getV();
const tMinus = vDelta / speedOrTime;
const isConstantTime = mode === AnimationMode.ConstantTime;
const step: FrameRequestCallback = () => {
if (t0 > Date.now()) {
setRunning(false);
rafRef.current = window.requestAnimationFrame(step);
return;
}
const tDelta = t0 + (isConstantTime ? speedOrTime : tMinus) - Date.now();
if (tDelta > 0) {
setRunning(true);
setV(
value -
(isConstantTime
? vDelta * (easeFn?.(tDelta / speedOrTime) ?? tDelta / speedOrTime)
: tDelta * speedOrTime)
);
rafRef.current = window.requestAnimationFrame(step);
} else {
setRunning(false);
setV(value);
}
};
rafRef.current = window.requestAnimationFrame(step);
return () => {
setRunning(false);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
window.cancelAnimationFrame(rafRef.current!);
};
}, [value, speedOrTime, delay, mode, getV, setV, easeFn]);
const setVImmediately = useCallback<typeof setV>(
(x) => {
setRunning(false);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
window.cancelAnimationFrame(rafRef.current!);
if (sync) {
setV((prev) => {
if (typeof x === 'function') {
// eslint-disable-next-line no-param-reassign
x = x(prev);
}
setValue?.(x);
return x;
});
} else {
setV(x);
setValue?.(x);
}
},
[setV, setValue, sync]
);
return [v, running, setVImmediately];
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment