{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "bg-animated-fractal-dot-grid", "description": "Background animated fractal dot grid with performance optimization and responsive design", "dependencies": [ "motion" ], "files": [ { "path": "registry/default/ui/bg-animated-fractal-dot-grid.tsx", "content": "\"use client\"\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from \"react\"\nimport { AnimatePresence, motion } from \"motion/react\"\n\ninterface FractalDotGridProps {\n /** Size of each dot in pixels */\n dotSize?: number\n /** Spacing between dots in pixels */\n dotSpacing?: number\n /** Opacity of dots (0-1) */\n dotOpacity?: number\n /** Intensity of the wave effect when hovering */\n waveIntensity?: number\n /** Radius of the wave effect in pixels */\n waveRadius?: number\n /** Color of the dots (supports any valid CSS color) */\n dotColor?: string\n /** Color of the dot glow effect (supports any valid CSS color) */\n glowColor?: string\n /** Enable or disable the noise overlay */\n enableNoise?: boolean\n /** Opacity of the noise overlay (0-1) */\n noiseOpacity?: number\n /** Enable or disable the mouse glow effect */\n enableMouseGlow?: boolean\n /** Set the initial performance level */\n initialPerformance?: \"low\" | \"medium\" | \"high\"\n}\n\nconst NoiseSVG = React.memo(() => (\n \n \n \n \n \n \n))\n\nNoiseSVG.displayName = \"NoiseSVG\"\n\nconst NoiseOverlay: React.FC<{ opacity: number }> = ({ opacity }) => (\n \n \n \n)\n\nconst useResponsive = () => {\n const [windowSize, setWindowSize] = useState({\n width: typeof window !== \"undefined\" ? window.innerWidth : 0,\n height: typeof window !== \"undefined\" ? window.innerHeight : 0,\n })\n\n useEffect(() => {\n const handleResize = () => {\n setWindowSize({\n width: window.innerWidth,\n height: window.innerHeight,\n })\n }\n\n window.addEventListener(\"resize\", handleResize)\n return () => window.removeEventListener(\"resize\", handleResize)\n }, [])\n\n return {\n isMobile: windowSize.width < 768,\n isTablet: windowSize.width >= 768 && windowSize.width < 1024,\n isDesktop: windowSize.width >= 1024,\n }\n}\n\nconst usePerformance = (\n initialPerformance: \"low\" | \"medium\" | \"high\" = \"medium\"\n) => {\n const [performance, setPerformance] = useState(initialPerformance)\n const [fps, setFps] = useState(60)\n\n useEffect(() => {\n let frameCount = 0\n let lastTime = globalThis.performance.now()\n let framerId: number\n\n const measureFps = (time: number) => {\n frameCount++\n if (time - lastTime > 1000) {\n setFps(Math.round((frameCount * 1000) / (time - lastTime)))\n frameCount = 0\n lastTime = time\n }\n framerId = requestAnimationFrame(measureFps)\n }\n\n framerId = requestAnimationFrame(measureFps)\n\n return () => cancelAnimationFrame(framerId)\n }, [])\n\n useEffect(() => {\n if (fps < 30 && performance !== \"low\") {\n setPerformance(\"low\")\n } else if (fps >= 30 && fps < 50 && performance !== \"medium\") {\n setPerformance(\"medium\")\n } else if (fps >= 50 && performance !== \"high\") {\n setPerformance(\"high\")\n }\n }, [fps, performance])\n\n return { performance, fps }\n}\n\nconst DotCanvas: React.FC<{\n dotSize: number\n dotSpacing: number\n dotOpacity: number\n waveIntensity: number\n waveRadius: number\n dotColor: string\n glowColor: string\n performance: \"low\" | \"medium\" | \"high\"\n mousePos: { x: number; y: number }\n}> = React.memo(\n ({\n dotSize,\n dotSpacing,\n dotOpacity,\n waveIntensity,\n waveRadius,\n dotColor,\n glowColor,\n performance,\n mousePos,\n }) => {\n const canvasRef = useRef(null)\n const animationRef = useRef(null)\n\n const drawDots = useCallback(\n (ctx: CanvasRenderingContext2D, time: number) => {\n const { width, height } = ctx.canvas\n ctx.clearRect(0, 0, width, height)\n\n const performanceSettings = {\n low: { skip: 3 },\n medium: { skip: 2 },\n high: { skip: 1 },\n }\n\n const skip = performanceSettings[performance].skip\n\n const cols = Math.ceil(width / dotSpacing)\n const rows = Math.ceil(height / dotSpacing)\n\n const centerX = mousePos.x * width\n const centerY = mousePos.y * height\n\n for (let i = 0; i < cols; i += skip) {\n for (let j = 0; j < rows; j += skip) {\n const x = i * dotSpacing\n const y = j * dotSpacing\n\n const distanceX = x - centerX\n const distanceY = y - centerY\n const distance = Math.sqrt(\n distanceX * distanceX + distanceY * distanceY\n )\n\n let dotX = x\n let dotY = y\n\n if (distance < waveRadius) {\n const waveStrength = Math.pow(1 - distance / waveRadius, 2)\n const angle = Math.atan2(distanceY, distanceX)\n const waveOffset =\n Math.sin(distance * 0.05 - time * 0.005) *\n waveIntensity *\n waveStrength\n dotX += Math.cos(angle) * waveOffset\n dotY += Math.sin(angle) * waveOffset\n\n const glowRadius = dotSize * (1 + waveStrength)\n const gradient = ctx.createRadialGradient(\n dotX,\n dotY,\n 0,\n dotX,\n dotY,\n glowRadius\n )\n gradient.addColorStop(\n 0,\n glowColor.replace(\"1)\", `${dotOpacity * (1 + waveStrength)})`)\n )\n gradient.addColorStop(1, glowColor.replace(\"1)\", \"0)\"))\n ctx.fillStyle = gradient\n } else {\n ctx.fillStyle = dotColor.replace(\"1)\", `${dotOpacity})`)\n }\n\n ctx.beginPath()\n ctx.arc(dotX, dotY, dotSize / 2, 0, Math.PI * 2)\n ctx.fill()\n }\n }\n },\n [\n dotSize,\n dotSpacing,\n dotOpacity,\n waveIntensity,\n waveRadius,\n dotColor,\n glowColor,\n performance,\n mousePos,\n ]\n )\n\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n const ctx = canvas.getContext(\"2d\")\n if (!ctx) return\n\n const resizeCanvas = () => {\n canvas.width = window.innerWidth\n canvas.height = window.innerHeight\n }\n\n resizeCanvas()\n window.addEventListener(\"resize\", resizeCanvas)\n\n let lastTime = 0\n const animate = (time: number) => {\n if (time - lastTime > 16) {\n drawDots(ctx, time)\n lastTime = time\n }\n animationRef.current = requestAnimationFrame(animate)\n }\n\n animationRef.current = requestAnimationFrame(animate)\n\n return () => {\n window.removeEventListener(\"resize\", resizeCanvas)\n if (animationRef.current) {\n cancelAnimationFrame(animationRef.current)\n }\n }\n }, [drawDots])\n\n return (\n \n )\n }\n)\n\nDotCanvas.displayName = \"DotCanvas\"\n\nconst MouseGlow: React.FC<{\n glowColor: string\n mousePos: { x: number; y: number }\n}> = React.memo(({ glowColor, mousePos }) => (\n <>\n \n \n \n))\n\nMouseGlow.displayName = \"MouseGlow\"\n\nexport function FractalDotGrid({\n dotSize = 4,\n dotSpacing = 20,\n dotOpacity = 0.3,\n waveIntensity = 30,\n waveRadius = 200,\n dotColor = \"rgba(100, 100, 255, 1)\",\n glowColor = \"rgba(100, 100, 255, 1)\",\n enableNoise = true,\n noiseOpacity = 0.03,\n enableMouseGlow = true,\n initialPerformance = \"medium\",\n}: FractalDotGridProps) {\n const containerRef = useRef(null)\n const { isMobile, isTablet } = useResponsive()\n const { performance } = usePerformance(initialPerformance)\n const [mousePos, setMousePos] = useState({ x: 0, y: 0 })\n\n const handleMouseMove = useCallback((event: MouseEvent) => {\n const { clientX, clientY } = event\n const { left, top, width, height } =\n containerRef.current?.getBoundingClientRect() ?? {\n left: 0,\n top: 0,\n width: 0,\n height: 0,\n }\n const x = (clientX - left) / width\n const y = (clientY - top) / height\n setMousePos({ x, y })\n }, [])\n\n useEffect(() => {\n window.addEventListener(\"mousemove\", handleMouseMove)\n return () => window.removeEventListener(\"mousemove\", handleMouseMove)\n }, [handleMouseMove])\n\n const responsiveDotSize = useMemo(() => {\n if (isMobile) return dotSize * 0.75\n if (isTablet) return dotSize * 0.9\n return dotSize\n }, [isMobile, isTablet, dotSize])\n\n const responsiveDotSpacing = useMemo(() => {\n if (isMobile) return dotSpacing * 1.5\n if (isTablet) return dotSpacing * 1.25\n return dotSpacing\n }, [isMobile, isTablet, dotSpacing])\n\n return (\n \n \n \n {enableNoise && }\n {enableMouseGlow && (\n \n )}\n \n \n )\n}\n\nexport default FractalDotGrid\n", "type": "registry:ui" } ], "type": "registry:ui" }