前端:优化图片加载长连接
This commit is contained in:
parent
3f52d609a3
commit
caa23c6ac5
@ -185,18 +185,26 @@ const customEase = (t: number) => {
|
|||||||
: 1 - Math.pow(-2 * t + 2, 3) / 2;
|
: 1 - Math.pow(-2 * t + 2, 3) / 2;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 在文件开头添加新的 LoaderStatus 接口
|
||||||
|
interface LoaderStatus {
|
||||||
|
isLoading: boolean;
|
||||||
|
hasError: boolean;
|
||||||
|
timeoutError: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改 ParticleImage 组件的 props 接口
|
||||||
interface ParticleImageProps {
|
interface ParticleImageProps {
|
||||||
src?: string;
|
src?: string;
|
||||||
|
status?: LoaderStatus;
|
||||||
onLoad?: () => void;
|
onLoad?: () => void;
|
||||||
onError?: () => void;
|
onAnimationComplete?: () => void;
|
||||||
performanceMode?: boolean; // 新增性能模式开关
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ParticleImage = ({
|
export const ParticleImage = ({
|
||||||
src,
|
src,
|
||||||
onLoad,
|
status,
|
||||||
onError,
|
onLoad,
|
||||||
performanceMode = false
|
onAnimationComplete
|
||||||
}: ParticleImageProps) => {
|
}: ParticleImageProps) => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const sceneRef = useRef<THREE.Scene>();
|
const sceneRef = useRef<THREE.Scene>();
|
||||||
@ -222,7 +230,7 @@ export const ParticleImage = ({
|
|||||||
// 更新渲染器大小
|
// 更新渲染器大小
|
||||||
rendererRef.current.setSize(width, height);
|
rendererRef.current.setSize(width, height);
|
||||||
|
|
||||||
// 只有当尺寸变化超过阈值时才重新生成粒子
|
// 只有当尺寸变化超过阈值时才重生成粒子
|
||||||
const currentSize = Math.min(width, height);
|
const currentSize = Math.min(width, height);
|
||||||
const previousSize = sceneRef.current.userData.previousSize || currentSize;
|
const previousSize = sceneRef.current.userData.previousSize || currentSize;
|
||||||
const sizeChange = Math.abs(currentSize - previousSize) / previousSize;
|
const sizeChange = Math.abs(currentSize - previousSize) / previousSize;
|
||||||
@ -400,7 +408,7 @@ export const ParticleImage = ({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建错误动画函数
|
// 建错误动<EFBFBD><EFBFBD>函数
|
||||||
const showErrorAnimation = () => {
|
const showErrorAnimation = () => {
|
||||||
if (!scene) return;
|
if (!scene) return;
|
||||||
|
|
||||||
@ -441,11 +449,9 @@ export const ParticleImage = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
onError?.();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载图片
|
// 加载图<EFBFBD><EFBFBD><EFBFBD>
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.crossOrigin = 'anonymous';
|
img.crossOrigin = 'anonymous';
|
||||||
|
|
||||||
@ -475,7 +481,7 @@ export const ParticleImage = ({
|
|||||||
sourceWidth = img.height * targetAspect;
|
sourceWidth = img.height * targetAspect;
|
||||||
sourceX = (img.width - sourceWidth) / 2;
|
sourceX = (img.width - sourceWidth) / 2;
|
||||||
} else {
|
} else {
|
||||||
// 图片较高,需要裁剪上下
|
// 图片较高,需要裁剪下
|
||||||
sourceHeight = img.width / targetAspect;
|
sourceHeight = img.width / targetAspect;
|
||||||
sourceY = (img.height - sourceHeight) / 2;
|
sourceY = (img.height - sourceHeight) / 2;
|
||||||
}
|
}
|
||||||
@ -574,7 +580,8 @@ export const ParticleImage = ({
|
|||||||
const checkComplete = () => {
|
const checkComplete = () => {
|
||||||
completedAnimations++;
|
completedAnimations++;
|
||||||
if (completedAnimations === totalAnimations) {
|
if (completedAnimations === totalAnimations) {
|
||||||
onLoad?.(); // 所有画完成后调用 onLoad
|
onLoad?.();
|
||||||
|
onAnimationComplete?.();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -583,8 +590,8 @@ export const ParticleImage = ({
|
|||||||
|
|
||||||
// 位置动画
|
// 位置动画
|
||||||
gsap.to(positionAttribute.array, {
|
gsap.to(positionAttribute.array, {
|
||||||
duration: 1.2 + Math.random() * 0.3, // 减少随机性范围
|
duration: 1.2 + Math.random() * 0.3,
|
||||||
delay: particle.delay, // 使用基于距离的延迟
|
delay: particle.delay,
|
||||||
[i3]: particle.originalX,
|
[i3]: particle.originalX,
|
||||||
[i3 + 1]: particle.originalY,
|
[i3 + 1]: particle.originalY,
|
||||||
[i3 + 2]: 0,
|
[i3 + 2]: 0,
|
||||||
@ -598,7 +605,7 @@ export const ParticleImage = ({
|
|||||||
// 颜色动画
|
// 颜色动画
|
||||||
gsap.to(colorAttribute.array, {
|
gsap.to(colorAttribute.array, {
|
||||||
duration: 1,
|
duration: 1,
|
||||||
delay: particle.delay + 0.2, // 稍微延迟颜色变化
|
delay: particle.delay + 0.2,
|
||||||
[i3]: particle.originalColor.r,
|
[i3]: particle.originalColor.r,
|
||||||
[i3 + 1]: particle.originalColor.g,
|
[i3 + 1]: particle.originalColor.g,
|
||||||
[i3 + 2]: particle.originalColor.b,
|
[i3 + 2]: particle.originalColor.b,
|
||||||
@ -668,7 +675,7 @@ export const ParticleImage = ({
|
|||||||
containerRef.current.removeChild(renderer.domElement);
|
containerRef.current.removeChild(renderer.domElement);
|
||||||
renderer.dispose();
|
renderer.dispose();
|
||||||
}
|
}
|
||||||
// 清除所有 GSAP 动画
|
// 清所有 GSAP 动画
|
||||||
gsap.killTweensOf('*');
|
gsap.killTweensOf('*');
|
||||||
|
|
||||||
// 移除 resize 监听
|
// 移除 resize 监听
|
||||||
@ -677,45 +684,122 @@ export const ParticleImage = ({
|
|||||||
}
|
}
|
||||||
window.removeEventListener('resize', handleResize);
|
window.removeEventListener('resize', handleResize);
|
||||||
};
|
};
|
||||||
}, [src, onError, handleResize]);
|
}, [src, handleResize, onLoad, onAnimationComplete]);
|
||||||
|
|
||||||
return <div ref={containerRef} className="w-full h-full" />;
|
return <div ref={containerRef} className="w-full h-full" />;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 图片加载组件
|
// 图片加载组件
|
||||||
export const ImageLoader = ({ src, alt, className }: {
|
export const ImageLoader = ({
|
||||||
|
src,
|
||||||
|
alt,
|
||||||
|
className
|
||||||
|
}: {
|
||||||
src?: string;
|
src?: string;
|
||||||
alt: string;
|
alt: string;
|
||||||
className: string;
|
className: string;
|
||||||
}) => {
|
}) => {
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [status, setStatus] = useState<LoaderStatus>({
|
||||||
const [hasError, setHasError] = useState(false);
|
isLoading: true,
|
||||||
|
hasError: false,
|
||||||
|
timeoutError: false
|
||||||
|
});
|
||||||
|
const [showImage, setShowImage] = useState(false);
|
||||||
|
const timeoutRef = useRef<NodeJS.Timeout>();
|
||||||
|
const loadingRef = useRef(false);
|
||||||
|
const imageRef = useRef<HTMLImageElement | null>(null);
|
||||||
|
|
||||||
|
// 处理图片预加载
|
||||||
|
const preloadImage = useCallback(() => {
|
||||||
|
if (!src || loadingRef.current) return;
|
||||||
|
|
||||||
|
loadingRef.current = true;
|
||||||
|
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus({
|
||||||
|
isLoading: true,
|
||||||
|
hasError: false,
|
||||||
|
timeoutError: false
|
||||||
|
});
|
||||||
|
setShowImage(false);
|
||||||
|
|
||||||
|
const img = new Image();
|
||||||
|
img.crossOrigin = 'anonymous';
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingRef.current = false;
|
||||||
|
// 图片加载成功后,不立即显示,等待粒子动画完成
|
||||||
|
imageRef.current = img;
|
||||||
|
setStatus({
|
||||||
|
isLoading: false,
|
||||||
|
hasError: false,
|
||||||
|
timeoutError: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onerror = () => {
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current);
|
||||||
|
}
|
||||||
|
loadingRef.current = false;
|
||||||
|
setStatus({
|
||||||
|
isLoading: false,
|
||||||
|
hasError: true,
|
||||||
|
timeoutError: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
img.src = src;
|
||||||
|
}, [src]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
preloadImage();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
loadingRef.current = false;
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [src, preloadImage]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-[140px] md:w-[180px] h-[140px] md:h-[180px] shrink-0 overflow-hidden">
|
<div className="relative w-[140px] md:w-[180px] h-[140px] md:h-[180px] shrink-0 overflow-hidden">
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-[rgb(10,37,77)] via-[rgb(8,27,57)] to-[rgb(2,8,23)] rounded-lg overflow-hidden">
|
<div className="absolute inset-0 bg-gradient-to-br from-[rgb(10,37,77)] via-[rgb(8,27,57)] to-[rgb(2,8,23)] rounded-lg overflow-hidden">
|
||||||
<ParticleImage
|
<ParticleImage
|
||||||
src={src}
|
src={src}
|
||||||
onLoad={() => setIsLoading(false)}
|
status={status}
|
||||||
onError={() => {
|
onLoad={() => {
|
||||||
setIsLoading(false);
|
// 粒子动画完成后,延迟显示图片
|
||||||
setHasError(true);
|
setTimeout(() => {
|
||||||
|
setShowImage(true);
|
||||||
|
}, 800); // 延迟时间可以根据需要调整
|
||||||
|
}}
|
||||||
|
onAnimationComplete={() => {
|
||||||
|
setShowImage(true);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{!hasError && (
|
{!status.hasError && !status.timeoutError && imageRef.current && (
|
||||||
<div className="absolute inset-0 rounded-lg overflow-hidden">
|
<div className="absolute inset-0 rounded-lg overflow-hidden">
|
||||||
<img
|
<img
|
||||||
src={src}
|
src={imageRef.current.src}
|
||||||
alt={alt}
|
alt={alt}
|
||||||
className={`
|
className={`
|
||||||
w-full h-full object-cover
|
w-full h-full object-cover
|
||||||
transition-opacity duration-1000
|
transition-opacity duration-1000
|
||||||
${className}
|
${className}
|
||||||
${isLoading ? 'opacity-0' : 'opacity-100'}
|
${showImage ? 'opacity-100' : 'opacity-0'}
|
||||||
`}
|
`}
|
||||||
style={{
|
style={{
|
||||||
visibility: isLoading ? 'hidden' : 'visible',
|
visibility: showImage ? 'visible' : 'hidden',
|
||||||
objectFit: 'cover',
|
objectFit: 'cover',
|
||||||
objectPosition: 'center'
|
objectPosition: 'center'
|
||||||
}}
|
}}
|
||||||
|
@ -6,9 +6,9 @@ import {
|
|||||||
LinkedInLogoIcon,
|
LinkedInLogoIcon,
|
||||||
EnvelopeClosedIcon,
|
EnvelopeClosedIcon,
|
||||||
} from "@radix-ui/react-icons";
|
} from "@radix-ui/react-icons";
|
||||||
import { ParticleImage } from "hooks/ParticleImage";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { gsap } from "gsap";
|
import { gsap } from "gsap";
|
||||||
|
import { ParticleImage } from "hooks/ParticleImage";
|
||||||
|
|
||||||
const socialLinks = [
|
const socialLinks = [
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user