53 lines
1.4 KiB
TypeScript
53 lines
1.4 KiB
TypeScript
|
|
import { useEffect } from "react";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Reusable left-to-right SVG stroke draw animation.
|
||
|
|
* Pass `enabled=false` to disable and clear dash styles (e.g. while dragging).
|
||
|
|
*/
|
||
|
|
export function useStrokeDrawAnimation(
|
||
|
|
pathRef: React.RefObject<SVGPathElement | null>,
|
||
|
|
pathD: string,
|
||
|
|
options?: {
|
||
|
|
enabled?: boolean;
|
||
|
|
durationMs?: number;
|
||
|
|
easing?: string;
|
||
|
|
},
|
||
|
|
) {
|
||
|
|
const enabled = options?.enabled ?? true;
|
||
|
|
const durationMs = options?.durationMs ?? 1350;
|
||
|
|
const easing = options?.easing ?? "cubic-bezier(0.33, 1, 0.68, 1)";
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const path = pathRef.current;
|
||
|
|
if (!path || !pathD) return;
|
||
|
|
|
||
|
|
if (!enabled) {
|
||
|
|
path.style.strokeDasharray = "";
|
||
|
|
path.style.strokeDashoffset = "";
|
||
|
|
path.style.transition = "";
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const length = path.getTotalLength();
|
||
|
|
if (!Number.isFinite(length) || length <= 0) return;
|
||
|
|
|
||
|
|
path.style.strokeDasharray = `${length}`;
|
||
|
|
path.style.strokeDashoffset = `${length}`;
|
||
|
|
path.style.transition = "none";
|
||
|
|
|
||
|
|
let cancelled = false;
|
||
|
|
const id = requestAnimationFrame(() => {
|
||
|
|
requestAnimationFrame(() => {
|
||
|
|
if (cancelled) return;
|
||
|
|
path.style.transition = `stroke-dashoffset ${durationMs}ms ${easing}`;
|
||
|
|
path.style.strokeDashoffset = "0";
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
return () => {
|
||
|
|
cancelled = true;
|
||
|
|
cancelAnimationFrame(id);
|
||
|
|
};
|
||
|
|
}, [pathRef, pathD, enabled, durationMs, easing]);
|
||
|
|
}
|