Optimize initial loading speed (LazyLoad).

This commit is contained in:
david_bai
2025-06-28 07:45:42 +08:00
parent 06c9c633c3
commit bcd37993e6
8 changed files with 120 additions and 78 deletions
@@ -0,0 +1,35 @@
"use client";
import { useInView } from "react-intersection-observer";
import { ReactNode, useEffect, useState } from "react";
interface LazyLoadWrapperProps {
children: ReactNode;
// 可以设置一个延迟,让组件在进入视口后稍微等一下再渲染,避免滚动过快时频繁渲染
// rootMargin 可以让组件在距离视口还有 N 像素时就开始加载
options?: {
triggerOnce?: boolean;
rootMargin?: string;
};
}
export default function LazyLoadWrapper({
children,
options = { triggerOnce: true, rootMargin: "200px" },
}: LazyLoadWrapperProps) {
const { ref, inView } = useInView(options);
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
if (inView && !isLoaded) {
setIsLoaded(true);
}
}, [inView, isLoaded]);
// 使用一个 div 包裹并附加 ref,同时可以设置最小高度,防止懒加载时页面布局跳动
return (
<div ref={ref} className="min-h-[200px]">
{isLoaded ? children : null}
</div>
);
}
+16 -35
View File
@@ -12,48 +12,29 @@ const YouTubePlayer: React.FC<YouTubePlayerProps> = ({
className = "",
}) => {
const [isPlaying, setIsPlaying] = useState<boolean>(false);
const [thumbnailLoaded, setThumbnailLoaded] = useState<boolean>(false);
const [currentThumbnail, setCurrentThumbnail] = useState<string>("");
const thumbnailUrl = `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`;
const fallbackUrl = `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`;
const localThumbnail = "/inActionThumbnail.webp";
const embedUrl = `https://www.youtube.com/embed/${videoId}?autoplay=1`;
useEffect(() => {
const img = new Image();
img.onload = () => {
setCurrentThumbnail(img.src);
setThumbnailLoaded(true);
};
img.onerror = () => {
img.src = fallbackUrl;
};
img.src = thumbnailUrl;
}, [videoId, thumbnailUrl, fallbackUrl]);
return (
<div className={`relative w-full max-w-5xl mx-auto ${className}`}>
<div className="relative pb-[56.25%]">
{!isPlaying ? (
<div className="absolute top-0 left-0 w-full h-full">
{currentThumbnail && (
<img
src={currentThumbnail}
alt="Video thumbnail"
className="w-full h-full object-cover"
/>
)}
{thumbnailLoaded && (
<button
onClick={() => setIsPlaying(true)}
className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2
bg-black bg-opacity-70 hover:bg-opacity-90 rounded-full p-4
transition-all duration-300 ease-in-out z-10"
aria-label="Play video"
>
<Play size={48} className="text-white" />
</button>
)}
<img
src={localThumbnail}
alt="Video preview"
className="w-full h-full object-cover"
/>
<button
onClick={() => setIsPlaying(true)}
className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2
bg-black bg-opacity-70 hover:bg-opacity-90 rounded-full p-4
transition-all duration-300 ease-in-out z-10"
aria-label="Play video"
>
<Play size={48} className="text-white" />
</button>
</div>
) : (
<iframe
+3 -9
View File
@@ -70,15 +70,9 @@ export default function HowItWorks({ messages }: PageContentProps) {
{/* Right Side - Demo Animation */}
<div className="w-full md:w-1/2">
<div className="bg-white rounded-lg shadow-lg overflow-hidden">
{/* The default Next.js image optimizer does not support handling of GIF animations */}
<Image
src="/HowItWorks.gif"
alt="How PrivyDrop Works"
unoptimized
width={700}
height={921}
className="mx-auto mb-6"
/>
<video autoPlay loop muted playsInline width="1920" height="75">
<source src="/HowItWorks.webm" type="video/webm" />
</video>
</div>
</div>
</div>