100 lines
2.3 KiB
TypeScript
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
|
|
});
|
|
}
|