UNPKG

react-gsap-animation-library

Version:

A comprehensive React component library that leverages GSAP for animations with official GSAP-React integration

301 lines (245 loc) 9.46 kB
import React, { useRef, useEffect } from 'react'; import { gsap } from 'gsap'; const AnimatedBackground = ({ children, pattern = 'dots', // 'dots', 'waves', 'grid', 'noise' color = 'rgba(0, 0, 0, 0.1)', backgroundColor = 'white', density = 20, speed = 1, interactive = true, className = '', style = {} }) => { const containerRef = useRef(null); const canvasRef = useRef(null); const animationRef = useRef(null); const mousePosition = useRef({ x: 0, y: 0 }); useEffect(() => { const container = containerRef.current; const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); // Resize canvas to match container const resizeCanvas = () => { canvas.width = container.offsetWidth; canvas.height = container.offsetHeight; }; resizeCanvas(); window.addEventListener('resize', resizeCanvas); // Track mouse position for interactive effects const handleMouseMove = (e) => { const rect = canvas.getBoundingClientRect(); mousePosition.current = { x: e.clientX - rect.left, y: e.clientY - rect.top }; }; if (interactive) { container.addEventListener('mousemove', handleMouseMove); } // Animation setup based on pattern let animate; let items = []; switch(pattern) { case 'dots': // Create dots const dotCount = Math.ceil((canvas.width * canvas.height) / (10000 / density)); for (let i = 0; i < dotCount; i++) { items.push({ x: Math.random() * canvas.width, y: Math.random() * canvas.height, radius: Math.random() * 3 + 1, vx: (Math.random() - 0.5) * speed, vy: (Math.random() - 0.5) * speed }); } animate = () => { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = backgroundColor; ctx.fillRect(0, 0, canvas.width, canvas.height); items.forEach(dot => { // Update position dot.x += dot.vx; dot.y += dot.vy; // Bounce off edges if (dot.x < 0 || dot.x > canvas.width) dot.vx *= -1; if (dot.y < 0 || dot.y > canvas.height) dot.vy *= -1; // Interactive effect - move away from cursor if (interactive) { const dx = mousePosition.current.x - dot.x; const dy = mousePosition.current.y - dot.y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < 60) { const angle = Math.atan2(dy, dx); const pushX = Math.cos(angle) * (60 - distance) * 0.02; const pushY = Math.sin(angle) * (60 - distance) * 0.02; dot.x -= pushX; dot.y -= pushY; } } // Draw dot ctx.beginPath(); ctx.arc(dot.x, dot.y, dot.radius, 0, Math.PI * 2); ctx.fillStyle = color; ctx.fill(); }); animationRef.current = requestAnimationFrame(animate); }; break; case 'waves': const waveCount = Math.ceil(canvas.height / (100 / density)); const waves = []; for (let i = 0; i < waveCount; i++) { waves.push({ y: (canvas.height / waveCount) * i + Math.random() * 20, amplitude: Math.random() * 10 + 5, frequency: Math.random() * 0.01 + 0.01, offset: Math.random() * 1000 }); } animate = () => { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = backgroundColor; ctx.fillRect(0, 0, canvas.width, canvas.height); waves.forEach(wave => { // Update offset wave.offset += speed * 0.05; // Draw wave ctx.beginPath(); ctx.moveTo(0, wave.y); for (let x = 0; x < canvas.width; x += 5) { const y = wave.y + Math.sin(x * wave.frequency + wave.offset) * wave.amplitude; ctx.lineTo(x, y); } ctx.lineTo(canvas.width, canvas.height); ctx.lineTo(0, canvas.height); ctx.closePath(); ctx.fillStyle = color; ctx.fill(); }); animationRef.current = requestAnimationFrame(animate); }; break; case 'grid': const cellSize = Math.floor(50 / (density / 10)); animate = () => { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = backgroundColor; ctx.fillRect(0, 0, canvas.width, canvas.height); const time = Date.now() * 0.001 * speed; // Draw grid ctx.strokeStyle = color; ctx.lineWidth = 1; // Vertical lines for (let x = 0; x <= canvas.width; x += cellSize) { const offsetX = Math.sin(time + x * 0.01) * 5; ctx.beginPath(); ctx.moveTo(x + offsetX, 0); ctx.lineTo(x + offsetX, canvas.height); ctx.stroke(); } // Horizontal lines for (let y = 0; y <= canvas.height; y += cellSize) { const offsetY = Math.cos(time + y * 0.01) * 5; ctx.beginPath(); ctx.moveTo(0, y + offsetY); ctx.lineTo(canvas.width, y + offsetY); ctx.stroke(); } animationRef.current = requestAnimationFrame(animate); }; break; case 'noise': animate = () => { const imageData = ctx.createImageData(canvas.width, canvas.height); const data = imageData.data; const time = Date.now() * 0.0001 * speed; const scale = density / 10; // Parse the color to get RGB values const colorMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/); let r, g, b, a = 1; if (colorMatch) { [, r, g, b, a] = colorMatch; r = parseInt(r); g = parseInt(g); b = parseInt(b); a = a ? parseFloat(a) : 1; } else { r = g = b = 0; } // Background const bgMatch = backgroundColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/); let bgR, bgG, bgB; if (bgMatch) { [, bgR, bgG, bgB] = bgMatch; bgR = parseInt(bgR); bgG = parseInt(bgG); bgB = parseInt(bgB); } else { bgR = bgG = bgB = 255; } for (let x = 0; x < canvas.width; x++) { for (let y = 0; y < canvas.height; y++) { const noise = Math.random(); const index = (y * canvas.width + x) * 4; // Simplex noise would be better here, but this is a simple demo const noiseValue = Math.sin(x / scale + time) * Math.cos(y / scale + time) * 0.5 + 0.5; // Mix noise with background color data[index] = bgR + (r - bgR) * noiseValue * a; data[index + 1] = bgG + (g - bgG) * noiseValue * a; data[index + 2] = bgB + (b - bgB) * noiseValue * a; data[index + 3] = 255; } } ctx.putImageData(imageData, 0, 0); animationRef.current = requestAnimationFrame(animate); }; break; default: // Default to dots animate = () => { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = backgroundColor; ctx.fillRect(0, 0, canvas.width, canvas.height); animationRef.current = requestAnimationFrame(animate); }; } // Start animation animate(); return () => { window.removeEventListener('resize', resizeCanvas); if (interactive) { container.removeEventListener('mousemove', handleMouseMove); } if (animationRef.current) { cancelAnimationFrame(animationRef.current); } }; }, [pattern, color, backgroundColor, density, speed, interactive]); const containerStyle = { position: 'relative', overflow: 'hidden', ...style }; const canvasStyle = { position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', zIndex: -1 }; const contentStyle = { position: 'relative', zIndex: 1 }; return ( <div ref={containerRef} className={className} style={containerStyle}> <canvas ref={canvasRef} style={canvasStyle} /> <div style={contentStyle}> {children} </div> </div> ); }; export default AnimatedBackground;