forked from 77media/video-flow
83 lines
1.9 KiB
TypeScript
83 lines
1.9 KiB
TypeScript
// Image3DFlipper.tsx
|
|
'use client';
|
|
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { useEffect, useState } from 'react';
|
|
|
|
type ImageBlurTransitionProps = {
|
|
className?: string;
|
|
src: string;
|
|
alt?: string;
|
|
width?: number | string;
|
|
height?: number | string;
|
|
enableAnimation?: boolean;
|
|
};
|
|
|
|
export default function ImageBlurTransition({
|
|
src,
|
|
alt = '',
|
|
width = 480,
|
|
height = 300,
|
|
className,
|
|
enableAnimation = true
|
|
}: ImageBlurTransitionProps) {
|
|
const [current, setCurrent] = useState(src);
|
|
const [isFlipping, setIsFlipping] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (src !== current) {
|
|
setIsFlipping(true);
|
|
const timeout = setTimeout(() => {
|
|
setCurrent(src);
|
|
setIsFlipping(false);
|
|
}, 150); // 时长 = exit 动画时长
|
|
return () => clearTimeout(timeout);
|
|
}
|
|
}, [src, current]);
|
|
|
|
return (
|
|
<div
|
|
className={`relative rounded-xl w-full h-fit ${className}`}
|
|
style={{
|
|
perspective: enableAnimation ? 1000 : 'none', // 只在启用动画时提供 3D 深度
|
|
}}
|
|
>
|
|
{enableAnimation ? (
|
|
<AnimatePresence mode="wait">
|
|
<motion.img
|
|
key={current}
|
|
src={current}
|
|
alt={alt}
|
|
className="w-full h-auto object-cover rounded-xl"
|
|
initial={{
|
|
opacity: 0,
|
|
filter: 'blur(8px)',
|
|
scale: 1.02,
|
|
}}
|
|
animate={{
|
|
opacity: 1,
|
|
filter: 'blur(0px)',
|
|
scale: 1,
|
|
}}
|
|
exit={{
|
|
opacity: 0,
|
|
filter: 'blur(4px)',
|
|
scale: 0.98,
|
|
}}
|
|
transition={{
|
|
duration: 0.3,
|
|
ease: 'easeInOut',
|
|
}}
|
|
/>
|
|
</AnimatePresence>
|
|
) : (
|
|
<img
|
|
src={src}
|
|
alt={alt}
|
|
className="w-full h-auto object-cover rounded-xl"
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|