72 lines
1.6 KiB
TypeScript
72 lines
1.6 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState, useEffect, useRef } from 'react';
|
|
import Image from 'next/image';
|
|
|
|
interface AnimatedImageProps {
|
|
src: string;
|
|
alt: string;
|
|
width?: number;
|
|
height?: number;
|
|
className?: string;
|
|
priority?: boolean;
|
|
}
|
|
|
|
export default function AnimatedImage({
|
|
src,
|
|
alt,
|
|
width = 800,
|
|
height = 600,
|
|
className = '',
|
|
priority = false,
|
|
}: AnimatedImageProps) {
|
|
const [isLoaded, setIsLoaded] = useState(false);
|
|
const [isInView, setIsInView] = useState(false);
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
entries.forEach((entry) => {
|
|
if (entry.isIntersecting) {
|
|
setIsInView(true);
|
|
observer.disconnect();
|
|
}
|
|
});
|
|
},
|
|
{ threshold: 0.1 }
|
|
);
|
|
|
|
if (containerRef.current) {
|
|
observer.observe(containerRef.current);
|
|
}
|
|
|
|
return () => observer.disconnect();
|
|
}, []);
|
|
|
|
return (
|
|
<div
|
|
ref={containerRef}
|
|
className={`relative overflow-hidden rounded-xl shadow-lg my-12 ${className}`}
|
|
>
|
|
<Image
|
|
src={src}
|
|
alt={alt}
|
|
width={width}
|
|
height={height}
|
|
className={`
|
|
duration-1000 ease-out w-full h-auto
|
|
${isLoaded && isInView ? 'scale-100 blur-0 opacity-100' : 'scale-110 blur-xl opacity-0'}
|
|
`}
|
|
onLoad={() => setIsLoaded(true)}
|
|
priority={priority}
|
|
/>
|
|
{alt && (
|
|
<p className="text-sm text-text-secondary text-center mt-3 italic px-4 pb-4">
|
|
{alt}
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|