UNPKG

reactbits-mcp-server

Version:

MCP Server for React Bits - Access 99+ React components with animations, backgrounds, and UI elements

153 lines (126 loc) 3.64 kB
import { useRef, useEffect, useCallback } from "react"; const ClickSpark = ({ sparkColor = "#fff", sparkSize = 10, sparkRadius = 15, sparkCount = 8, duration = 400, easing = "ease-out", extraScale = 1.0, children }) => { const canvasRef = useRef(null); const sparksRef = useRef([]); const startTimeRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const parent = canvas.parentElement; if (!parent) return; let resizeTimeout; const resizeCanvas = () => { const { width, height } = parent.getBoundingClientRect(); if (canvas.width !== width || canvas.height !== height) { canvas.width = width; canvas.height = height; } }; const handleResize = () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(resizeCanvas, 100); }; const ro = new ResizeObserver(handleResize); ro.observe(parent); resizeCanvas(); return () => { ro.disconnect(); clearTimeout(resizeTimeout); }; }, []); const easeFunc = useCallback( (t) => { switch (easing) { case "linear": return t; case "ease-in": return t * t; case "ease-in-out": return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; default: return t * (2 - t); } }, [easing] ); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); let animationId; const draw = (timestamp) => { if (!startTimeRef.current) { startTimeRef.current = timestamp; } ctx.clearRect(0, 0, canvas.width, canvas.height); sparksRef.current = sparksRef.current.filter((spark) => { const elapsed = timestamp - spark.startTime; if (elapsed >= duration) { return false; } const progress = elapsed / duration; const eased = easeFunc(progress); const distance = eased * sparkRadius * extraScale; const lineLength = sparkSize * (1 - eased); const x1 = spark.x + distance * Math.cos(spark.angle); const y1 = spark.y + distance * Math.sin(spark.angle); const x2 = spark.x + (distance + lineLength) * Math.cos(spark.angle); const y2 = spark.y + (distance + lineLength) * Math.sin(spark.angle); ctx.strokeStyle = sparkColor; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); return true; }); animationId = requestAnimationFrame(draw); }; animationId = requestAnimationFrame(draw); return () => { cancelAnimationFrame(animationId); }; }, [ sparkColor, sparkSize, sparkRadius, sparkCount, duration, easeFunc, extraScale, ]); const handleClick = (e) => { const canvas = canvasRef.current; if (!canvas) return; const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const now = performance.now(); const newSparks = Array.from({ length: sparkCount }, (_, i) => ({ x, y, angle: (2 * Math.PI * i) / sparkCount, startTime: now, })); sparksRef.current.push(...newSparks); }; return ( <div className="relative w-full h-full" onClick={handleClick}> <canvas ref={canvasRef} className="w-full h-full block absolute top-0 left-0 select-none pointer-events-none" /> {children} </div> ); }; export default ClickSpark;