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
JSX
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;