Files
NianAIGC/lib/ui/motion.ts
2026-05-29 10:26:02 +08:00

100 lines
2.3 KiB
TypeScript

"use client";
import { gsap } from "gsap";
type ScopeRef = { current: Element | null };
const ENTER_DURATION = 0.28;
const EXIT_DURATION = 0.18;
const ENTER_EASE = "power2.out";
const EXIT_EASE = "power1.in";
export function prefersReducedMotion() {
return typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}
export function runScopedMotion(scopeRef: ScopeRef, setup: (scope: Element) => void) {
const scope = scopeRef.current;
if (!scope || prefersReducedMotion()) return undefined;
const context = gsap.context(() => setup(scope), scope);
return () => context.revert();
}
export function revealChildren(scope: Element, selector = "[data-animate]") {
if (prefersReducedMotion()) return;
const items = gsap.utils.toArray<HTMLElement>(selector, scope);
if (!items.length) return;
gsap.fromTo(
items,
{ autoAlpha: 0, y: 14 },
{
autoAlpha: 1,
y: 0,
duration: ENTER_DURATION,
ease: ENTER_EASE,
stagger: 0.045,
clearProps: "transform,opacity,visibility"
}
);
}
export function crossfadeIn(element: Element | null, y = 10) {
if (!element || prefersReducedMotion()) return;
gsap.fromTo(
element,
{ autoAlpha: 0, y },
{
autoAlpha: 1,
y: 0,
duration: ENTER_DURATION,
ease: ENTER_EASE,
clearProps: "transform,opacity,visibility"
}
);
}
export function pulseFeedback(element: Element | null) {
if (!element || prefersReducedMotion()) return;
gsap.fromTo(
element,
{ scale: 0.985 },
{
scale: 1,
duration: 0.22,
ease: ENTER_EASE,
clearProps: "transform"
}
);
}
export function modalEnter(element: Element | null) {
if (!element || prefersReducedMotion()) return;
gsap.fromTo(
element,
{ autoAlpha: 0, scale: 0.98, y: 10 },
{
autoAlpha: 1,
scale: 1,
y: 0,
duration: ENTER_DURATION,
ease: ENTER_EASE,
clearProps: "transform,opacity,visibility"
}
);
}
export function modalExit(element: Element | null, onComplete: () => void) {
if (!element || prefersReducedMotion()) {
onComplete();
return;
}
gsap.to(element, {
autoAlpha: 0,
scale: 0.985,
y: 6,
duration: EXIT_DURATION,
ease: EXIT_EASE,
onComplete
});
}