diff --git a/registry/default/magicui/particles.tsx b/registry/default/magicui/particles.tsx index 52613e34..febb13f5 100644 --- a/registry/default/magicui/particles.tsx +++ b/registry/default/magicui/particles.tsx @@ -1,7 +1,13 @@ "use client"; import { cn } from "@/lib/utils"; -import React, { useEffect, useRef, useState } from "react"; +import React, { + useEffect, + useRef, + useState, + useMemo, + useCallback, +} from "react"; interface MousePosition { x: number; @@ -40,6 +46,7 @@ interface ParticlesProps { vx?: number; vy?: number; } + function hexToRgb(hex: string): number[] { hex = hex.replace("#", ""); @@ -77,45 +84,7 @@ const Particles: React.FC = ({ const canvasSize = useRef<{ w: number; h: number }>({ w: 0, h: 0 }); const dpr = typeof window !== "undefined" ? window.devicePixelRatio : 1; - useEffect(() => { - if (canvasRef.current) { - context.current = canvasRef.current.getContext("2d"); - } - initCanvas(); - animate(); - window.addEventListener("resize", initCanvas); - - return () => { - window.removeEventListener("resize", initCanvas); - }; - }, [color]); - - useEffect(() => { - onMouseMove(); - }, [mousePosition.x, mousePosition.y]); - - useEffect(() => { - initCanvas(); - }, [refresh]); - - const initCanvas = () => { - resizeCanvas(); - drawParticles(); - }; - - const onMouseMove = () => { - if (canvasRef.current) { - const rect = canvasRef.current.getBoundingClientRect(); - const { w, h } = canvasSize.current; - const x = mousePosition.x - rect.left - w / 2; - const y = mousePosition.y - rect.top - h / 2; - const inside = x < w / 2 && x > -w / 2 && y < h / 2 && y > -h / 2; - if (inside) { - mouse.current.x = x; - mouse.current.y = y; - } - } - }; + const rgb = useMemo(() => hexToRgb(color), [color]); type Circle = { x: number; @@ -130,7 +99,7 @@ const Particles: React.FC = ({ magnetism: number; }; - const resizeCanvas = () => { + const resizeCanvas = useCallback(() => { if (canvasContainerRef.current && canvasRef.current && context.current) { circles.current.length = 0; canvasSize.current.w = canvasContainerRef.current.offsetWidth; @@ -141,9 +110,9 @@ const Particles: React.FC = ({ canvasRef.current.style.height = `${canvasSize.current.h}px`; context.current.scale(dpr, dpr); } - }; + }, [dpr]); - const circleParams = (): Circle => { + const circleParams = useCallback((): Circle => { const x = Math.floor(Math.random() * canvasSize.current.w); const y = Math.floor(Math.random() * canvasSize.current.h); const translateX = 0; @@ -166,27 +135,28 @@ const Particles: React.FC = ({ dy, magnetism, }; - }; + }, [size]); - const rgb = hexToRgb(color); + const drawCircle = useCallback( + (circle: Circle, update = false) => { + if (context.current) { + const { x, y, translateX, translateY, size, alpha } = circle; + context.current.translate(translateX, translateY); + context.current.beginPath(); + context.current.arc(x, y, size, 0, 2 * Math.PI); + context.current.fillStyle = `rgba(${rgb.join(", ")}, ${alpha})`; + context.current.fill(); + context.current.setTransform(dpr, 0, 0, dpr, 0, 0); - const drawCircle = (circle: Circle, update = false) => { - if (context.current) { - const { x, y, translateX, translateY, size, alpha } = circle; - context.current.translate(translateX, translateY); - context.current.beginPath(); - context.current.arc(x, y, size, 0, 2 * Math.PI); - context.current.fillStyle = `rgba(${rgb.join(", ")}, ${alpha})`; - context.current.fill(); - context.current.setTransform(dpr, 0, 0, dpr, 0, 0); - - if (!update) { - circles.current.push(circle); + if (!update) { + circles.current.push(circle); + } } - } - }; + }, + [dpr, rgb], + ); - const clearContext = () => { + const clearContext = useCallback(() => { if (context.current) { context.current.clearRect( 0, @@ -195,30 +165,35 @@ const Particles: React.FC = ({ canvasSize.current.h, ); } - }; + }, []); - const drawParticles = () => { + const drawParticles = useCallback(() => { clearContext(); const particleCount = quantity; for (let i = 0; i < particleCount; i++) { const circle = circleParams(); drawCircle(circle); } - }; + }, [quantity, circleParams, clearContext, drawCircle]); - const remapValue = ( - value: number, - start1: number, - end1: number, - start2: number, - end2: number, - ): number => { - const remapped = - ((value - start1) * (end2 - start2)) / (end1 - start1) + start2; - return remapped > 0 ? remapped : 0; - }; + const remapValue = useCallback( + ( + value: number, + start1: number, + end1: number, + start2: number, + end2: number, + ): number => { + const remapped = + ((value - start1) * (end2 - start2)) / (end1 - start1) + start2; + return remapped > 0 ? remapped : 0; + }, + [], + ); + + const animate = useCallback(() => { + if (!context.current) return; - const animate = () => { clearContext(); circles.current.forEach((circle: Circle, i: number) => { // Handle the alpha value @@ -267,7 +242,54 @@ const Particles: React.FC = ({ } }); window.requestAnimationFrame(animate); - }; + }, [ + clearContext, + circleParams, + drawCircle, + ease, + remapValue, + staticity, + vx, + vy, + ]); + + const initCanvas = useCallback(() => { + resizeCanvas(); + drawParticles(); + }, [resizeCanvas, drawParticles]); + + const onMouseMove = useCallback(() => { + if (canvasRef.current) { + const rect = canvasRef.current.getBoundingClientRect(); + const { w, h } = canvasSize.current; + const x = mousePosition.x - rect.left - w / 2; + const y = mousePosition.y - rect.top - h / 2; + const inside = x < w / 2 && x > -w / 2 && y < h / 2 && y > -h / 2; + if (inside) { + mouse.current.x = x; + mouse.current.y = y; + } + } + }, [mousePosition]); + + useEffect(() => { + if (canvasRef.current) { + context.current = canvasRef.current.getContext("2d"); + initCanvas(); + animate(); + + if (refresh) { + window.addEventListener("resize", initCanvas); + return () => { + window.removeEventListener("resize", initCanvas); + }; + } + } + }, [color, refresh, initCanvas, animate]); + + useEffect(() => { + onMouseMove(); + }, [mousePosition.x, mousePosition.y, onMouseMove]); return (