UNPKG

reactbits-mcp-server

Version:

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

221 lines (182 loc) 6.48 kB
import { useRef, useEffect } from 'react'; const LetterGlitch = ({ glitchColors = ['#2b4539', '#61dca3', '#61b3dc'], glitchSpeed = 50, centerVignette = false, outerVignette = true, smooth = true, }) => { const canvasRef = useRef(null); const animationRef = useRef(null); const letters = useRef([]); const grid = useRef({ columns: 0, rows: 0 }); const context = useRef(null); const lastGlitchTime = useRef(Date.now()); const fontSize = 16; const charWidth = 10; const charHeight = 20; const lettersAndSymbols = [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '!', '@', '#', '$', '&', '*', '(', ')', '-', '_', '+', '=', '/', '[', ']', '{', '}', ';', ':', '<', '>', ',', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ]; const getRandomChar = () => { return lettersAndSymbols[Math.floor(Math.random() * lettersAndSymbols.length)]; }; const getRandomColor = () => { return glitchColors[Math.floor(Math.random() * glitchColors.length)]; }; const hexToRgb = (hex) => { const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; hex = hex.replace(shorthandRegex, (m, r, g, b) => { return r + r + g + g + b + b; }); const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; }; const interpolateColor = (start, end, factor) => { const result = { r: Math.round(start.r + (end.r - start.r) * factor), g: Math.round(start.g + (end.g - start.g) * factor), b: Math.round(start.b + (end.b - start.b) * factor), }; return `rgb(${result.r}, ${result.g}, ${result.b})`; }; const calculateGrid = (width, height) => { const columns = Math.ceil(width / charWidth); const rows = Math.ceil(height / charHeight); return { columns, rows }; }; const initializeLetters = (columns, rows) => { grid.current = { columns, rows }; const totalLetters = columns * rows; letters.current = Array.from({ length: totalLetters }, () => ({ char: getRandomChar(), color: getRandomColor(), targetColor: getRandomColor(), colorProgress: 1, })); }; const resizeCanvas = () => { const canvas = canvasRef.current; if (!canvas) return; const parent = canvas.parentElement; if (!parent) return; const dpr = window.devicePixelRatio || 1; const rect = parent.getBoundingClientRect(); canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; canvas.style.width = `${rect.width}px`; canvas.style.height = `${rect.height}px`; if (context.current) { context.current.setTransform(dpr, 0, 0, dpr, 0, 0); } const { columns, rows } = calculateGrid(rect.width, rect.height); initializeLetters(columns, rows); drawLetters(); }; const drawLetters = () => { if (!context.current || letters.current.length === 0) return; const ctx = context.current; const { width, height } = canvasRef.current.getBoundingClientRect(); ctx.clearRect(0, 0, width, height); ctx.font = `${fontSize}px monospace`; ctx.textBaseline = 'top'; letters.current.forEach((letter, index) => { const x = (index % grid.current.columns) * charWidth; const y = Math.floor(index / grid.current.columns) * charHeight; ctx.fillStyle = letter.color; ctx.fillText(letter.char, x, y); }); }; const updateLetters = () => { if (!letters.current || letters.current.length === 0) return; const updateCount = Math.max(1, Math.floor(letters.current.length * 0.05)); for (let i = 0; i < updateCount; i++) { const index = Math.floor(Math.random() * letters.current.length); if (!letters.current[index]) continue; letters.current[index].char = getRandomChar(); letters.current[index].targetColor = getRandomColor(); if (!smooth) { letters.current[index].color = letters.current[index].targetColor; letters.current[index].colorProgress = 1; } else { letters.current[index].colorProgress = 0; } } }; const handleSmoothTransitions = () => { let needsRedraw = false; letters.current.forEach((letter) => { if (letter.colorProgress < 1) { letter.colorProgress += 0.05; if (letter.colorProgress > 1) letter.colorProgress = 1; const startRgb = hexToRgb(letter.color); const endRgb = hexToRgb(letter.targetColor); if (startRgb && endRgb) { letter.color = interpolateColor(startRgb, endRgb, letter.colorProgress); needsRedraw = true; } } }); if (needsRedraw) { drawLetters(); } }; const animate = () => { const now = Date.now(); if (now - lastGlitchTime.current >= glitchSpeed) { updateLetters(); drawLetters(); lastGlitchTime.current = now; } if (smooth) { handleSmoothTransitions(); } animationRef.current = requestAnimationFrame(animate); }; useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; context.current = canvas.getContext('2d'); resizeCanvas(); animate(); let resizeTimeout; const handleResize = () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => { cancelAnimationFrame(animationRef.current); resizeCanvas(); animate(); }, 100); }; window.addEventListener('resize', handleResize); return () => { cancelAnimationFrame(animationRef.current); window.removeEventListener('resize', handleResize); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [glitchSpeed, smooth]); return ( <div className="relative w-full h-full bg-black overflow-hidden"> <canvas ref={canvasRef} className="block w-full h-full" /> {outerVignette && ( <div className="absolute top-0 left-0 w-full h-full pointer-events-none bg-[radial-gradient(circle,_rgba(0,0,0,0)_60%,_rgba(0,0,0,1)_100%)]" ></div> )} {centerVignette && ( <div className="absolute top-0 left-0 w-full h-full pointer-events-none bg-[radial-gradient(circle,_rgba(0,0,0,0.8)_0%,_rgba(0,0,0,0)_60%)]" ></div> )} </div> ); }; export default LetterGlitch;