Skip to content

Instantly share code, notes, and snippets.

@kabukki
Last active February 12, 2024 22:55
Show Gist options
  • Save kabukki/94328a9cf806e19926a0c80725563476 to your computer and use it in GitHub Desktop.
Save kabukki/94328a9cf806e19926a0c80725563476 to your computer and use it in GitHub Desktop.
useTweenState
import { useRef, useState } from 'react';
export type EasingFunction = (progress: number) => number;
export type InterpolateFunction <T> = (a: T, b: T) => (time: number) => T
/**
* Returns a state that can be tweened with an interpolator and easing function.
* - https://d3js.org/d3-interpolate
* - https://d3js.org/d3-ease
*/
export function useTweenState <T> (initialState: T | (() => T), options: {
duration: number
interpolate: InterpolateFunction<T>
easing: EasingFunction
}) {
const [state, setState] = useState(initialState);
const raf = useRef<ReturnType<typeof requestAnimationFrame> | null>(null);
const setStateWrapper: typeof setState = (value) => {
const target = typeof value === 'function' ? (value as (previous: T) => T)(state) : value as T;
const interpolator = options.interpolate(state, target);
let start: DOMHighResTimeStamp = null;
const animate: FrameRequestCallback = (time) => {
if (start === null) {
start = time;
}
const diff = time - start;
const progress = Math.max(0, Math.min(diff / options.duration, 1));
const interpolated = interpolator(options.easing(progress));
setState(interpolated);
if (diff < options.duration) {
raf.current = requestAnimationFrame(animate);
} else {
raf.current = null;
}
};
if (state === target) {
return;
}
if (!!raf.current) {
cancelAnimationFrame(raf.current);
raf.current = null;
}
raf.current = requestAnimationFrame(animate);
};
return [state, setStateWrapper] as const;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment