forked from 77media/video-flow
69 lines
1.6 KiB
TypeScript
69 lines
1.6 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;
|
|
};
|
|
|
|
export default function ImageBlurTransition({ src, alt = '', width = 480, height = 300, className }: 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 ${className}`}
|
|
style={{
|
|
width,
|
|
height,
|
|
perspective: 1000, // 关键:提供 3D 深度
|
|
}}
|
|
>
|
|
<AnimatePresence mode="wait">
|
|
<motion.img
|
|
key={current}
|
|
src={current}
|
|
alt={alt}
|
|
className="absolute 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>
|
|
</div>
|
|
);
|
|
}
|