前端:优化加载图片动画

This commit is contained in:
lsy 2024-12-05 02:43:58 +08:00
parent b29fe30c71
commit 76ee04bdde

View File

@ -42,8 +42,10 @@ const createErrorParticles = (width: number, height: number) => {
const randomY = y + (Math.random() - 0.5) * randomOffset;
// 缩放到适合容器的大小
const scaledX = randomX * (width * 0.3);
const scaledY = randomY * (height * 0.3);
const size = Math.min(width, height);
const scaleFactor = size * 0.3;
const scaledX = randomX * scaleFactor;
const scaledY = randomY * scaleFactor;
particles.push({
x: scaledX,
@ -57,9 +59,9 @@ const createErrorParticles = (width: number, height: number) => {
// 随机初始位置
positionArray.push(
(Math.random() - 0.5) * width * 2,
(Math.random() - 0.5) * height * 2,
(Math.random() - 0.5) * 100
(Math.random() - 0.5) * size * 2,
(Math.random() - 0.5) * size * 2,
(Math.random() - 0.5) * 50
);
// 随机初始颜色
@ -67,21 +69,29 @@ const createErrorParticles = (width: number, height: number) => {
}
});
return { particles, positionArray, colorArray };
const size = Math.min(width, height);
const scale = size / 200;
const particleSize = Math.max(1.2, scale * 1.2);
return { particles, positionArray, colorArray, particleSize };
};
// 添加笑脸粒子生成函数
// 修改 createSmileParticles 函数
const createSmileParticles = (width: number, height: number) => {
const particles: Particle[] = [];
const positionArray: number[] = [];
const colorArray: number[] = [];
// 调整笑脸参数
const radius = Math.min(width, height) * 0.35; // 脸部大小
const particlesCount = 400; // 轮廓粒子数量
// 根据容器大小动态调整参数
const size = Math.min(width, height);
const scale = size / 200; // 基准子
// 修改颜色为更深的金色
const particleColor = new THREE.Color(0.8, 0.6, 0); // 更深的金色
// 调整笑脸参数
const radius = size * 0.25; // 更合理的脸部大小比例
const particlesCount = Math.floor(150 * scale); // 减少粒子数量
const particleSize = Math.max(1.2, scale * 1.2); // 确保粒子大小适应屏幕
const particleColor = new THREE.Color(0.8, 0.6, 0);
// 创建圆形脸部轮廓
for (let i = 0; i < particlesCount / 2; i++) {
@ -98,24 +108,25 @@ const createSmileParticles = (width: number, height: number) => {
});
positionArray.push(
(Math.random() - 0.5) * width * 2,
(Math.random() - 0.5) * height * 2,
(Math.random() - 0.5) * 100
(Math.random() - 0.5) * size * 4,
(Math.random() - 0.5) * size * 4,
(Math.random() - 0.5) * 150
);
colorArray.push(particleColor.r, particleColor.g, particleColor.b);
}
// 眼睛参数
const eyeOffset = radius * 0.2; // 眼睛水平间距
const eyeY = radius * 0.2; // 眼睛垂直位置
const eyeSize = radius * 0.08; // 眼睛大小
// 眼睛参数调整
const eyeOffset = radius * 0.3; // 增加眼睛间距
const eyeY = radius * 0.15; // 调整眼睛垂直位置
const eyeSize = radius * 0.12; // 增加眼睛大小
// 眼睛粒子数量也要根据比例调整
const eyeParticles = Math.floor(20 * scale);
// 创建实心眼睛
[-1, 1].forEach(side => {
// 创建密集的点来填充眼睛
for (let i = 0; i < 30; i++) {
const r = Math.random() * eyeSize; // 随机半径
const angle = Math.random() * Math.PI * 2; // 随机角度
for (let i = 0; i < eyeParticles; i++) {
const r = Math.random() * eyeSize;
const angle = Math.random() * Math.PI * 2;
const x = side * eyeOffset + Math.cos(angle) * r;
const y = eyeY + Math.sin(angle) * r;
@ -128,18 +139,18 @@ const createSmileParticles = (width: number, height: number) => {
});
positionArray.push(
(Math.random() - 0.5) * width * 2,
(Math.random() - 0.5) * height * 2,
(Math.random() - 0.5) * 100
(Math.random() - 0.5) * size * 4,
(Math.random() - 0.5) * size * 4,
(Math.random() - 0.5) * 150
);
colorArray.push(particleColor.r, particleColor.g, particleColor.b);
}
});
// 嘴巴参数
const smileWidth = radius * 0.5; // 嘴巴宽度
const smileY = -radius * 0.3; // 将嘴巴位置向下移动更多
const smilePoints = 40; // 嘴巴粒子数量
// 嘴巴参数调整
const smileWidth = radius * 0.6; // 增加嘴巴宽度
const smileY = -radius * 0.25; // 调整嘴巴位置
const smilePoints = Math.floor(30 * scale); // 根据大小调整嘴巴粒子数量
// 创建微笑
for (let i = 0; i < smilePoints; i++) {
@ -158,14 +169,14 @@ const createSmileParticles = (width: number, height: number) => {
});
positionArray.push(
(Math.random() - 0.5) * width * 2,
(Math.random() - 0.5) * height * 2,
(Math.random() - 0.5) * 100
(Math.random() - 0.5) * size * 4,
(Math.random() - 0.5) * size * 4,
(Math.random() - 0.5) * 150
);
colorArray.push(particleColor.r, particleColor.g, particleColor.b);
}
return { particles, positionArray, colorArray };
return { particles, positionArray, colorArray, particleSize };
};
// 在文件开头添加新的 helper 函数
@ -179,33 +190,104 @@ const customEase = (t: number) => {
: 1 - Math.pow(-2 * t + 2, 3) / 2;
};
export const ParticleImage = ({ src, onLoad, onError }: {
interface ParticleImageProps {
src?: string;
onLoad?: () => void;
onError?: () => void;
}) => {
performanceMode?: boolean; // 新增性能模式开关
}
export const ParticleImage = ({
src,
onLoad,
onError,
performanceMode = false // 默认关闭
}: ParticleImageProps) => {
const containerRef = useRef<HTMLDivElement>(null);
const sceneRef = useRef<THREE.Scene>();
const cameraRef = useRef<THREE.OrthographicCamera>();
const rendererRef = useRef<THREE.WebGLRenderer>();
const animationFrameRef = useRef<number>();
// 添加 resize 处理函数
const width = containerRef.current?.offsetWidth || 0;
const height = containerRef.current?.offsetHeight || 0;
const size = Math.min(width, height);
const scale = size / 200; // 基准因子
// 在性能模式下使用更保守的参数
const particleCount = performanceMode ?
Math.floor(100 * scale) :
Math.floor(200 * scale);
// 添 resize 处理函数
const handleResize = useCallback(() => {
if (!containerRef.current || !cameraRef.current || !rendererRef.current) return;
if (!containerRef.current || !cameraRef.current || !rendererRef.current || !sceneRef.current) return;
const width = containerRef.current.offsetWidth;
const height = containerRef.current.offsetHeight;
const camera = cameraRef.current;
camera.left = width / -2.1;
camera.right = width / 2.1;
camera.top = height / 2.1;
camera.bottom = height / -2.1;
camera.left = width / -2;
camera.right = width / 2;
camera.top = height / 2;
camera.bottom = height / -2;
camera.updateProjectionMatrix();
rendererRef.current.setSize(width, height);
}, []);
// 重新生成粒子
if (src === '') {
// 清除现有的 GSAP 动画
gsap.killTweensOf('*');
// 重新生成笑脸
const { particles, positionArray, colorArray, particleSize } = createSmileParticles(width, height);
const material = new THREE.PointsMaterial({
size: particleSize,
vertexColors: true,
transparent: true,
opacity: 1,
sizeAttenuation: true,
blending: THREE.AdditiveBlending,
depthWrite: false,
depthTest: false
});
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positionArray, 3));
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colorArray, 3));
sceneRef.current.clear();
const points = new THREE.Points(geometry, material);
sceneRef.current.add(points);
// 修改这部分,添加动画而不是直接设置位置
const positionAttribute = geometry.attributes.position;
particles.forEach((particle, i) => {
const i3 = i * 3;
const distanceToCenter = Math.sqrt(
Math.pow(particle.originalX, 2) +
Math.pow(particle.originalY, 2)
);
const maxDistance = Math.sqrt(Math.pow(width/2, 2) + Math.pow(height/2, 2));
const normalizedDistance = distanceToCenter / maxDistance;
gsap.to(positionAttribute.array, {
duration: 0.8,
delay: normalizedDistance * 0.6,
[i3]: particle.originalX,
[i3 + 1]: particle.originalY,
[i3 + 2]: 0,
ease: "sine.inOut",
onUpdate: () => {
positionAttribute.needsUpdate = true;
}
});
});
}
}, [src]);
useEffect(() => {
if (!containerRef.current) return;
@ -218,14 +300,15 @@ export const ParticleImage = ({ src, onLoad, onError }: {
sceneRef.current = scene;
const camera = new THREE.OrthographicCamera(
width / -1.5, // 扩大视野范围,从 -2 改为 -1.5
width / 1.5, // 扩大视野范围,从 2 改为 1.5
height / 1.5, // 扩大视野范围
height / -1.5, // 扩大视野范围
width / -2,
width / 2,
height / 2,
height / -2,
1,
1000
);
camera.position.z = 100;
camera.position.z = 500;
camera.lookAt(new THREE.Vector3(0, 0, 0));
cameraRef.current = camera;
const renderer = new THREE.WebGLRenderer({
@ -237,40 +320,49 @@ export const ParticleImage = ({ src, onLoad, onError }: {
rendererRef.current = renderer;
containerRef.current.appendChild(renderer.domElement);
const geometry = new THREE.BufferGeometry();
const material = new THREE.PointsMaterial({
size: 1.2,
vertexColors: true,
transparent: true,
opacity: 1,
sizeAttenuation: true,
blending: THREE.AdditiveBlending,
depthWrite: false,
depthTest: false
});
// 检查是否应该显示笑脸
if (src === '') {
console.log('Showing smile animation');
const { particles, positionArray, colorArray } = createSmileParticles(width, height);
const { particles, positionArray, colorArray, particleSize } = createSmileParticles(width, height);
const material = new THREE.PointsMaterial({
size: particleSize,
vertexColors: true,
transparent: true,
opacity: 1,
sizeAttenuation: true,
blending: THREE.AdditiveBlending,
depthWrite: false,
depthTest: false
});
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positionArray, 3));
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colorArray, 3));
const points = new THREE.Points(geometry, material);
scene.add(points);
// 修改动画效果
const positionAttribute = geometry.attributes.position;
// 计算到中心的距离用于延迟
particles.forEach((particle, i) => {
const i3 = i * 3;
const distanceToCenter = Math.sqrt(
Math.pow(particle.originalX, 2) +
Math.pow(particle.originalY, 2)
);
const maxDistance = Math.sqrt(Math.pow(width/2, 2) + Math.pow(height/2, 2));
const normalizedDistance = distanceToCenter / maxDistance;
gsap.to(positionAttribute.array, {
duration: 1,
delay: Math.random() * 0.3,
duration: 0.8,
delay: normalizedDistance * 0.6,
[i3]: particle.originalX,
[i3 + 1]: particle.originalY,
[i3 + 2]: 0,
ease: "back.out(1.7)",
ease: "sine.inOut",
onUpdate: () => {
positionAttribute.needsUpdate = true;
}
@ -286,19 +378,56 @@ export const ParticleImage = ({ src, onLoad, onError }: {
};
animate();
return;
// 添加 resize 监听
const resizeObserver = new ResizeObserver(() => {
if (containerRef.current) {
handleResize();
}
});
resizeObserver.observe(containerRef.current);
// 添加窗口 resize 监听
window.addEventListener('resize', handleResize);
return () => {
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
}
if (renderer && containerRef.current) {
containerRef.current.removeChild(renderer.domElement);
renderer.dispose();
}
gsap.killTweensOf('*');
if (containerRef.current) {
resizeObserver.unobserve(containerRef.current);
}
window.removeEventListener('resize', handleResize);
};
}
// 创建错误动画函数
const showErrorAnimation = () => {
if (!scene) return;
const { particles, positionArray, colorArray } = createErrorParticles(width, height);
const { particles, positionArray, colorArray, particleSize } = createErrorParticles(width, height);
const material = new THREE.PointsMaterial({
size: particleSize,
vertexColors: true,
transparent: true,
opacity: 1,
sizeAttenuation: true,
blending: THREE.AdditiveBlending,
depthWrite: false,
depthTest: false
});
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positionArray, 3));
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colorArray, 3));
const points = new THREE.Points(geometry, material);
scene.clear(); // 清除现有内容
scene.clear();
scene.add(points);
const positionAttribute = geometry.attributes.position;
@ -360,14 +489,14 @@ export const ParticleImage = ({ src, onLoad, onError }: {
offsetX = (width - drawWidth) / 2;
}
// 绘制
// 绘制
ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
const imageData = ctx.getImageData(0, 0, width, height);
const particles: Particle[] = [];
const positionArray = [];
const colorArray = [];
const samplingGap = Math.ceil(Math.max(width, height) / 100); // 动态采样间隔,确保粒子数量适中
const samplingGap = Math.ceil(Math.max(width, height) / 80); // 减少采样密度
// 采样图片像素
for (let y = 0; y < height; y += samplingGap) {
@ -412,6 +541,22 @@ export const ParticleImage = ({ src, onLoad, onError }: {
}
}
}
const size = Math.min(width, height);
const scale = size / 200;
const particleSize = Math.max(1.2, scale * 1.2);
const geometry = new THREE.BufferGeometry();
const material = new THREE.PointsMaterial({
size: particleSize,
vertexColors: true,
transparent: true,
opacity: 1,
sizeAttenuation: true,
blending: THREE.AdditiveBlending,
depthWrite: false,
depthTest: false
});
scene.clear();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positionArray, 3));
@ -487,6 +632,8 @@ export const ParticleImage = ({ src, onLoad, onError }: {
duration: 0.8
}, "-=0.6"); // 提前开始消失
}
return { particles, positionArray, colorArray, particleSize };
}
};
@ -523,7 +670,7 @@ export const ParticleImage = ({ src, onLoad, onError }: {
renderer.dispose();
}
// 清除所有 GSAP 动画
gsap.killTweensOf(geometry.attributes.position?.array);
gsap.killTweensOf('*');
// 移除 resize 监听
if (containerRef.current) {