diff --git a/src/components/effects/seal-animation-enhanced.tsx b/src/components/effects/seal-animation-enhanced.tsx new file mode 100644 index 0000000..83acb01 --- /dev/null +++ b/src/components/effects/seal-animation-enhanced.tsx @@ -0,0 +1,178 @@ +'use client'; + +import { useEffect, useRef, useCallback, useState } from 'react'; + +interface Particle { + x: number; + y: number; + targetX: number; + targetY: number; + vx: number; + vy: number; + size: number; + opacity: number; + color: string; + life: number; + maxLife: number; + stage: 'idle' | 'dispersing' | 'reforming'; +} + +interface SealAnimationEnhancedProps { + width?: number; + height?: number; + particleCount?: number; + colors?: string[]; + sealText?: string; + animationStages?: boolean; + onStageChange?: (stage: string) => void; + className?: string; +} + +export function SealAnimationEnhanced({ + width = 300, + height = 300, + particleCount = 150, + colors = ['#C41E3A', '#D4A574', '#8B4513'], + sealText = '睿新', + animationStages = true, + onStageChange, + className = '', +}: SealAnimationEnhancedProps) { + const canvasRef = useRef(null); + const particlesRef = useRef([]); + const animationRef = useRef(null); + const [currentStage, setCurrentStage] = useState<'idle' | 'dispersing' | 'reforming'>('idle'); + const stageTimerRef = useRef(null); + + const createSealShape = useCallback((width: number, height: number) => { + const centerX = width / 2; + const centerY = height / 2; + const sealSize = Math.min(width, height) * 0.35; + const particles: { x: number; y: number }[] = []; + + for (let i = 0; i < particleCount; i++) { + const angle = (i / particleCount) * Math.PI * 2; + const radius = sealSize * (0.8 + Math.random() * 0.4); + particles.push({ + x: centerX + Math.cos(angle) * radius * (Math.random() > 0.5 ? 1 : 0.8), + y: centerY + Math.sin(angle) * radius * (Math.random() > 0.5 ? 1 : 0.8), + }); + } + + return particles; + }, [particleCount]); + + const createParticle = useCallback( + (x: number, y: number, targetX: number, targetY: number): Particle => { + const color = colors[Math.floor(Math.random() * colors.length)]; + const size = 2 + Math.random() * 3; + const maxLife = 200 + Math.random() * 100; + + return { + x, + y, + targetX, + targetY, + vx: (Math.random() - 0.5) * 2, + vy: (Math.random() - 0.5) * 2, + size, + opacity: 0.6 + Math.random() * 0.4, + color, + life: 0, + maxLife, + stage: 'idle', + }; + }, + [colors] + ); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + canvas.width = width; + canvas.height = height; + + const sealPositions = createSealShape(width, height); + particlesRef.current = sealPositions.map((pos, i) => + createParticle(pos.x, pos.y, pos.x, pos.y) + ); + + if (animationStages) { + stageTimerRef.current = setTimeout(() => { + setCurrentStage('dispersing'); + onStageChange?.('dispersing'); + + particlesRef.current.forEach(p => { + p.vx = (Math.random() - 0.5) * 4; + p.vy = (Math.random() - 0.5) * 4; + p.stage = 'dispersing'; + }); + + setTimeout(() => { + setCurrentStage('reforming'); + onStageChange?.('reforming'); + + particlesRef.current.forEach(p => { + p.stage = 'reforming'; + }); + + setTimeout(() => { + setCurrentStage('idle'); + onStageChange?.('idle'); + }, 3000); + }, 2000); + }, 3000); + } + + const animate = () => { + ctx.clearRect(0, 0, width, height); + + particlesRef.current.forEach((particle) => { + if (particle.stage === 'reforming') { + const dx = particle.targetX - particle.x; + const dy = particle.targetY - particle.y; + particle.vx += dx * 0.02; + particle.vy += dy * 0.02; + particle.vx *= 0.95; + particle.vy *= 0.95; + } + + particle.x += particle.vx; + particle.y += particle.vy; + particle.life++; + + ctx.beginPath(); + ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2); + ctx.fillStyle = particle.color; + ctx.globalAlpha = particle.opacity; + ctx.fill(); + ctx.globalAlpha = 1; + }); + + animationRef.current = requestAnimationFrame(animate); + }; + + animate(); + + return () => { + if (animationRef.current) { + cancelAnimationFrame(animationRef.current); + } + if (stageTimerRef.current) { + clearTimeout(stageTimerRef.current); + } + }; + }, [width, height, createSealShape, createParticle, animationStages, onStageChange]); + + return ( + + ); +}