ly0303521

修改页面中缩略图和视频的加载策略,提高加载速度

@@ -12,6 +12,8 @@ interface ImageCardProps { @@ -12,6 +12,8 @@ interface ImageCardProps {
12 12
13 const ImageCard: React.FC<ImageCardProps> = ({ image, onClick, onLike, currentUser, isVideo = false }) => { 13 const ImageCard: React.FC<ImageCardProps> = ({ image, onClick, onLike, currentUser, isVideo = false }) => {
14 const [isLoaded, setIsLoaded] = useState(false); 14 const [isLoaded, setIsLoaded] = useState(false);
  15 + const [isHovered, setIsHovered] = useState(false);
  16 + const [videoStarted, setVideoStarted] = useState(false);
15 const videoRef = useRef<HTMLVideoElement>(null); 17 const videoRef = useRef<HTMLVideoElement>(null);
16 18
17 const handleLike = (e: React.MouseEvent) => { 19 const handleLike = (e: React.MouseEvent) => {
@@ -20,60 +22,75 @@ const ImageCard: React.FC<ImageCardProps> = ({ image, onClick, onLike, currentUs @@ -20,60 +22,75 @@ const ImageCard: React.FC<ImageCardProps> = ({ image, onClick, onLike, currentUs
20 }; 22 };
21 23
22 const handleMouseEnter = () => { 24 const handleMouseEnter = () => {
23 - if (videoRef.current) {  
24 - videoRef.current.currentTime = 0;  
25 - videoRef.current.play().catch(e => console.error("Video play failed", e));  
26 - } 25 + setIsHovered(true);
  26 + // Video will be played via autoPlay once rendered
27 } 27 }
28 28
29 const handleMouseLeave = () => { 29 const handleMouseLeave = () => {
30 - if (videoRef.current) {  
31 - videoRef.current.pause();  
32 - videoRef.current.currentTime = videoRef.current.duration || 0;  
33 - }  
34 - }  
35 -  
36 - const handleVideoMetadata = () => {  
37 - if (videoRef.current) {  
38 - videoRef.current.currentTime = videoRef.current.duration || 0;  
39 - } 30 + setIsHovered(false);
  31 + setVideoStarted(false);
40 } 32 }
41 33
42 - // If it's a video with a thumbnail, we want to show it immediately (via poster)  
43 - // instead of waiting for the video file to load metadata. 34 + // Content is considered "ready" if the main image/video is loaded
  35 + // OR if we have a thumbnail to show for a video.
44 const showContent = isLoaded || (isVideo && !!image.thumbnail); 36 const showContent = isLoaded || (isVideo && !!image.thumbnail);
45 37
46 return ( 38 return (
47 <div 39 <div
48 - className="group relative mb-4 break-inside-avoid rounded-2xl overflow-hidden bg-gray-200 cursor-zoom-in shadow-sm hover:shadow-xl transition-all duration-300" 40 + className="group relative mb-4 break-inside-avoid rounded-2xl overflow-hidden bg-gray-100 cursor-zoom-in shadow-sm hover:shadow-xl transition-all duration-300"
49 onClick={() => onClick(image)} 41 onClick={() => onClick(image)}
50 onMouseEnter={handleMouseEnter} 42 onMouseEnter={handleMouseEnter}
51 onMouseLeave={handleMouseLeave} 43 onMouseLeave={handleMouseLeave}
52 > 44 >
53 {/* Placeholder / Skeleton */} 45 {/* Placeholder / Skeleton */}
54 {!showContent && ( 46 {!showContent && (
55 - <div className="absolute inset-0 bg-gray-200 animate-pulse min-h-[200px]" /> 47 + <div className="absolute inset-0 bg-gray-200 animate-pulse min-h-[150px]" />
56 )} 48 )}
57 49
58 {/* Main Content */} 50 {/* Main Content */}
59 {isVideo ? ( 51 {isVideo ? (
  52 + <div className="relative w-full overflow-hidden bg-gray-100">
  53 + {/* Always show thumbnail first */}
  54 + <img
  55 + src={image.thumbnail || (isVideo ? '' : image.url)}
  56 + alt={image.prompt}
  57 + className={`w-full h-auto object-cover transition-opacity duration-500 ${videoStarted ? 'opacity-0' : 'opacity-100'} ${!image.thumbnail && isVideo ? 'opacity-0' : ''}`}
  58 + onLoad={() => setIsLoaded(true)}
  59 + loading="lazy"
  60 + />
  61 +
  62 + {isVideo && !image.thumbnail && !videoStarted && (
  63 + <div className="absolute inset-0 flex items-center justify-center bg-gray-200 text-gray-400">
  64 + <Video size={32} />
  65 + </div>
  66 + )}
  67 +
  68 + {/* Load video only on hover */}
  69 + {isHovered && (
60 <video 70 <video
61 ref={videoRef} 71 ref={videoRef}
62 src={image.url} 72 src={image.url}
63 - poster={image.thumbnail} // Use thumbnail as poster  
64 - className={`w-full h-auto object-cover transition-opacity duration-300 ${showContent ? 'opacity-100' : 'opacity-0'}`}  
65 - onLoadedMetadata={handleVideoMetadata}  
66 - onLoadedData={() => setIsLoaded(true)} 73 + className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-300 ${videoStarted ? 'opacity-100' : 'opacity-0'}`}
  74 + onLoadedData={() => setVideoStarted(true)}
  75 + autoPlay
67 loop 76 loop
68 muted 77 muted
69 playsInline 78 playsInline
70 - preload="metadata"  
71 /> 79 />
  80 + )}
  81 +
  82 + {/* Video Indicator Icon (When not hovered) */}
  83 + {!isHovered && (
  84 + <div className="absolute top-3 left-3 p-1.5 bg-black/30 backdrop-blur-md rounded-lg text-white/90">
  85 + <Video size={14} />
  86 + </div>
  87 + )}
  88 + </div>
72 ) : ( 89 ) : (
73 <img 90 <img
74 src={image.url} 91 src={image.url}
75 alt={image.prompt} 92 alt={image.prompt}
76 - className={`w-full h-auto object-cover transition-transform duration-700 ease-in-out group-hover:scale-105 ${showContent ? 'opacity-100' : 'opacity-0'}`} 93 + className={`w-full h-auto object-cover transition-transform duration-700 ease-in-out group-hover:scale-105 ${isLoaded ? 'opacity-100' : 'opacity-0'}`}
77 onLoad={() => setIsLoaded(true)} 94 onLoad={() => setIsLoaded(true)}
78 loading="lazy" 95 loading="lazy"
79 /> 96 />