repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
stars
latest
clone command
git clone gitlawb://did:key:z6MkvfHn...poLu/gitcatgit clone gitlawb://did:key:z6MkvfHn.../gitcata815108csync from playground1d ago| #1 | import { useEffect, useRef, useCallback } from 'react'; |
| #2 | |
| #3 | interface Particle { |
| #4 | x: number; |
| #5 | y: number; |
| #6 | vx: number; |
| #7 | vy: number; |
| #8 | size: number; |
| #9 | color: string; |
| #10 | life: number; |
| #11 | maxLife: number; |
| #12 | rotation: number; |
| #13 | rotationSpeed: number; |
| #14 | type: 'circle' | 'star' | 'confetti'; |
| #15 | } |
| #16 | |
| #17 | function drawStar(ctx: CanvasRenderingContext2D, r: number) { |
| #18 | ctx.beginPath(); |
| #19 | for (let i = 0; i < 10; i++) { |
| #20 | const radius = i % 2 === 0 ? r : r * 0.45; |
| #21 | const angle = (Math.PI * i) / 5 - Math.PI / 2; |
| #22 | const method = i === 0 ? 'moveTo' : 'lineTo'; |
| #23 | ctx[method](radius * Math.cos(angle), radius * Math.sin(angle)); |
| #24 | } |
| #25 | ctx.closePath(); |
| #26 | ctx.fill(); |
| #27 | } |
| #28 | |
| #29 | export function useParticles() { |
| #30 | const canvasRef = useRef<HTMLCanvasElement>(null); |
| #31 | const particles = useRef<Particle[]>([]); |
| #32 | const rafId = useRef(0); |
| #33 | |
| #34 | useEffect(() => { |
| #35 | const canvas = canvasRef.current!; |
| #36 | const ctx = canvas.getContext('2d')!; |
| #37 | |
| #38 | const resize = () => { |
| #39 | canvas.width = window.innerWidth; |
| #40 | canvas.height = window.innerHeight; |
| #41 | }; |
| #42 | resize(); |
| #43 | window.addEventListener('resize', resize); |
| #44 | |
| #45 | const loop = () => { |
| #46 | ctx.clearRect(0, 0, canvas.width, canvas.height); |
| #47 | |
| #48 | particles.current = particles.current.filter((p) => { |
| #49 | p.life -= 1 / (p.maxLife * 60); |
| #50 | if (p.life <= 0) return false; |
| #51 | |
| #52 | p.x += p.vx; |
| #53 | p.y += p.vy; |
| #54 | p.vy += 0.14; |
| #55 | p.vx *= 0.99; |
| #56 | p.rotation += p.rotationSpeed; |
| #57 | |
| #58 | ctx.save(); |
| #59 | ctx.globalAlpha = Math.max(0, p.life); |
| #60 | ctx.translate(p.x, p.y); |
| #61 | ctx.rotate(p.rotation); |
| #62 | ctx.fillStyle = p.color; |
| #63 | |
| #64 | if (p.type === 'circle') { |
| #65 | ctx.beginPath(); |
| #66 | ctx.arc(0, 0, p.size, 0, Math.PI * 2); |
| #67 | ctx.fill(); |
| #68 | } else if (p.type === 'star') { |
| #69 | drawStar(ctx, p.size); |
| #70 | } else { |
| #71 | ctx.fillRect(-p.size / 2, -p.size / 4, p.size, p.size / 2); |
| #72 | } |
| #73 | |
| #74 | ctx.restore(); |
| #75 | return true; |
| #76 | }); |
| #77 | |
| #78 | rafId.current = requestAnimationFrame(loop); |
| #79 | }; |
| #80 | |
| #81 | rafId.current = requestAnimationFrame(loop); |
| #82 | return () => { |
| #83 | cancelAnimationFrame(rafId.current); |
| #84 | window.removeEventListener('resize', resize); |
| #85 | }; |
| #86 | }, []); |
| #87 | |
| #88 | const spawn = useCallback( |
| #89 | (x: number, y: number, count: number = 8, intensity: number = 1) => { |
| #90 | if (particles.current.length > 400) { |
| #91 | particles.current = particles.current.slice(-200); |
| #92 | } |
| #93 | const colors = [ |
| #94 | '#FFD700', |
| #95 | '#FF69B4', |
| #96 | '#FF1493', |
| #97 | '#FFFFFF', |
| #98 | '#00CED1', |
| #99 | '#FF6347', |
| #100 | '#7FFF00', |
| #101 | ]; |
| #102 | for (let i = 0; i < count; i++) { |
| #103 | const angle = |
| #104 | (Math.PI * 2 * i) / count + (Math.random() - 0.5) * 0.8; |
| #105 | const speed = (3 + Math.random() * 5) * intensity; |
| #106 | const type: Particle['type'] = |
| #107 | intensity > 2 && Math.random() > 0.5 |
| #108 | ? 'confetti' |
| #109 | : Math.random() > 0.3 |
| #110 | ? 'circle' |
| #111 | : 'star'; |
| #112 | particles.current.push({ |
| #113 | x, |
| #114 | y, |
| #115 | vx: Math.cos(angle) * speed, |
| #116 | vy: Math.sin(angle) * speed - 3 * intensity, |
| #117 | size: (4 + Math.random() * 6) * Math.min(intensity, 2), |
| #118 | color: colors[Math.floor(Math.random() * colors.length)], |
| #119 | life: 1, |
| #120 | maxLife: 0.6 + Math.random() * 0.6, |
| #121 | rotation: Math.random() * Math.PI * 2, |
| #122 | rotationSpeed: (Math.random() - 0.5) * 0.3, |
| #123 | type, |
| #124 | }); |
| #125 | } |
| #126 | }, |
| #127 | [], |
| #128 | ); |
| #129 | |
| #130 | return { canvasRef, spawn }; |
| #131 | } |
| #132 |