120 lines
2.9 KiB
TypeScript
120 lines
2.9 KiB
TypeScript
'use client';
|
|
|
|
import * as React from 'react';
|
|
import {
|
|
AnimatePresence,
|
|
motion,
|
|
useInView,
|
|
type HTMLMotionProps,
|
|
type UseInViewOptions,
|
|
type Transition,
|
|
type Variant,
|
|
} from 'motion/react';
|
|
|
|
type MotionEffectProps = HTMLMotionProps<'div'> & {
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
transition?: Transition;
|
|
delay?: number;
|
|
inView?: boolean;
|
|
inViewMargin?: UseInViewOptions['margin'];
|
|
inViewOnce?: boolean;
|
|
blur?: string | boolean;
|
|
slide?:
|
|
| {
|
|
direction?: 'up' | 'down' | 'left' | 'right';
|
|
offset?: number;
|
|
}
|
|
| boolean;
|
|
fade?: { initialOpacity?: number; opacity?: number } | boolean;
|
|
zoom?:
|
|
| {
|
|
initialScale?: number;
|
|
scale?: number;
|
|
}
|
|
| boolean;
|
|
};
|
|
|
|
function MotionEffect({
|
|
ref,
|
|
children,
|
|
className,
|
|
transition = { type: 'spring', stiffness: 200, damping: 20 },
|
|
delay = 0,
|
|
inView = false,
|
|
inViewMargin = '0px',
|
|
inViewOnce = true,
|
|
blur = false,
|
|
slide = false,
|
|
fade = false,
|
|
zoom = false,
|
|
...props
|
|
}: MotionEffectProps) {
|
|
const localRef = React.useRef<HTMLDivElement>(null);
|
|
React.useImperativeHandle(ref, () => localRef.current as HTMLDivElement);
|
|
|
|
const inViewResult = useInView(localRef, {
|
|
once: inViewOnce,
|
|
margin: inViewMargin,
|
|
});
|
|
const isInView = !inView || inViewResult;
|
|
|
|
const hiddenVariant: Variant = {};
|
|
const visibleVariant: Variant = {};
|
|
|
|
if (slide) {
|
|
const offset = typeof slide === 'boolean' ? 100 : (slide.offset ?? 100);
|
|
const direction =
|
|
typeof slide === 'boolean' ? 'left' : (slide.direction ?? 'left');
|
|
const axis = direction === 'up' || direction === 'down' ? 'y' : 'x';
|
|
hiddenVariant[axis] =
|
|
direction === 'left' || direction === 'up' ? -offset : offset;
|
|
visibleVariant[axis] = 0;
|
|
}
|
|
|
|
if (fade) {
|
|
hiddenVariant.opacity =
|
|
typeof fade === 'boolean' ? 0 : (fade.initialOpacity ?? 0);
|
|
visibleVariant.opacity =
|
|
typeof fade === 'boolean' ? 1 : (fade.opacity ?? 1);
|
|
}
|
|
|
|
if (zoom) {
|
|
hiddenVariant.scale =
|
|
typeof zoom === 'boolean' ? 0.5 : (zoom.initialScale ?? 0.5);
|
|
visibleVariant.scale = typeof zoom === 'boolean' ? 1 : (zoom.scale ?? 1);
|
|
}
|
|
|
|
if (blur) {
|
|
hiddenVariant.filter =
|
|
typeof blur === 'boolean' ? 'blur(10px)' : `blur(${blur})`;
|
|
visibleVariant.filter = 'blur(0px)';
|
|
}
|
|
|
|
return (
|
|
<AnimatePresence>
|
|
<motion.div
|
|
ref={localRef}
|
|
data-slot="motion-effect"
|
|
initial="hidden"
|
|
animate={isInView ? 'visible' : 'hidden'}
|
|
exit="hidden"
|
|
variants={{
|
|
hidden: hiddenVariant,
|
|
visible: visibleVariant,
|
|
}}
|
|
transition={{
|
|
...transition,
|
|
delay: (transition?.delay ?? 0) + delay,
|
|
}}
|
|
className={className}
|
|
{...props}
|
|
>
|
|
{children}
|
|
</motion.div>
|
|
</AnimatePresence>
|
|
);
|
|
}
|
|
|
|
export { MotionEffect, type MotionEffectProps };
|