Last active
July 4, 2024 03:15
-
-
Save chrisvogt/7581d8f6c9343ddec5dd9dfc23d0435f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useEffect, useRef } from 'react'; | |
const AnimatedBackground = () => { | |
const canvasRef = useRef(null); | |
useEffect(() => { | |
const canvas = canvasRef.current; | |
const ctx = canvas.getContext('2d'); | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
// Predefined complex gradient colors | |
const gradients = [ | |
[ | |
{ position: 0, color: 'rgba(255, 183, 248, 1)' }, | |
{ position: 0, color: 'rgba(218, 214, 237, 1)' }, | |
{ position: 0, color: 'rgba(169, 255, 224, 1)' }, | |
{ position: 0, color: 'rgba(88, 197, 210, 0.86)' }, | |
{ position: 0.1, color: 'rgba(47, 201, 192, 1)' }, | |
{ position: 0.75, color: 'rgba(192, 187, 255, 0.51)' }, | |
{ position: 0.99, color: 'rgba(255, 255, 255, 0)' }, | |
{ position: 0.99, color: 'rgba(153, 224, 255, 0)' } | |
], | |
[ | |
{ position: 0.06, color: 'rgba(255, 204, 169, 1)' }, | |
{ position: 0.13, color: 'rgba(255, 127, 255, 0)' }, | |
{ position: 0.37, color: 'rgba(192, 187, 255, 0.37)' }, | |
{ position: 0.82, color: 'rgba(255, 204, 169, 1)' }, | |
{ position: 0.99, color: 'rgba(255, 255, 255, 1)' } | |
] | |
]; | |
class Circle { | |
constructor(x, y, radius, gradientStops) { | |
this.x = x; | |
this.y = y; | |
this.radius = radius; | |
this.gradientStops = gradientStops; | |
this.dx = (Math.random() - 0.5) * 0.715; // Increase speed by another 30% | |
this.dy = (Math.random() - 0.5) * 0.715; // Increase speed by another 30% | |
// console.log(`Circle created at (${this.x}, ${this.y}) with radius=${this.radius}, dx=${this.dx}, dy=${this.dy}`); | |
} | |
draw() { | |
ctx.globalAlpha = 0.75; // Set transparency to 75% | |
const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.radius); | |
this.gradientStops.forEach(stop => { | |
gradient.addColorStop(stop.position, stop.color); | |
}); | |
ctx.beginPath(); | |
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); | |
ctx.fillStyle = gradient; | |
ctx.fill(); | |
ctx.closePath(); | |
ctx.globalAlpha = 1.0; // Reset transparency to 100% | |
} | |
update(circles) { | |
// console.log(`Circle at (${this.x}, ${this.y}) with dx=${this.dx}, dy=${this.dy}`); | |
this.x += this.dx; | |
this.y += this.dy; | |
if (this.x + this.radius > canvas.width || this.x - this.radius < 0) { | |
// console.log(`Circle hit horizontal boundary at (${this.x}, ${this.y})`); | |
this.dx = -this.dx; | |
} | |
if (this.y + this.radius > canvas.height || this.y - this.radius < 0) { | |
// console.log(`Circle hit vertical boundary at (${this.y}, ${this.y})`); | |
this.dy = -this.dy; | |
} | |
for (let i = 0; i < circles.length; i++) { | |
if (this === circles[i]) continue; | |
const dx = this.x - circles[i].x; | |
const dy = this.y - circles[i].y; | |
const distance = Math.sqrt(dx * dx + dy * dy); | |
if (distance < this.radius + circles[i].radius) { | |
// Resolve overlap | |
const overlap = this.radius + circles[i].radius - distance; | |
const angle = Math.atan2(dy, dx); | |
const moveX = overlap * Math.cos(angle) / 2; | |
const moveY = overlap * Math.sin(angle) / 2; | |
this.x += moveX; | |
this.y += moveY; | |
circles[i].x -= moveX; | |
circles[i].y -= moveY; | |
// Swap velocities | |
this.dx = -this.dx; | |
this.dy = -this.dy; | |
circles[i].dx = -circles[i].dx; | |
circles[i].dy = -circles[i].dy; | |
// console.log(`Circle collision resolved at (${this.x}, ${this.y})`); | |
} | |
} | |
this.draw(); | |
} | |
} | |
const circles = []; | |
// One large circle | |
const largeRadius = Math.random() * 200 + 250; // Up to 450 | |
const largeX = Math.random() * (canvas.width - largeRadius * 2) + largeRadius; | |
const largeY = Math.random() * (canvas.height - largeRadius * 2) + largeRadius; | |
const largeGradient = gradients[0]; // Use the first gradient for the large circle | |
const largeCircle = new Circle(largeX, largeY, largeRadius, largeGradient); | |
circles.push(largeCircle); | |
// Smaller circles | |
for (let i = 0; i < 3; i++) { | |
const radius = Math.random() * 70 + 80; // Smaller circles | |
const x = Math.random() * (canvas.width - radius * 2) + radius; | |
const y = Math.random() * (canvas.height - radius * 2) + radius; | |
const gradient = gradients[(i + 1) % 2]; // Alternate gradients between the two sets | |
const smallCircle = new Circle(x, y, radius, gradient); | |
circles.push(smallCircle); | |
} | |
function animate() { | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
circles.forEach(circle => circle.update(circles)); | |
requestAnimationFrame(animate); | |
} | |
animate(); | |
// Handle resizing of the window | |
const handleResize = () => { | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
}; | |
window.addEventListener('resize', handleResize); | |
return () => { | |
window.removeEventListener('resize', handleResize); | |
}; | |
}, []); | |
return ( | |
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }}> | |
<canvas ref={canvasRef} style={{ display: 'block', position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }}></canvas> | |
<div style={{ | |
position: 'absolute', | |
top: 0, | |
left: 0, | |
width: '100%', | |
height: '100%', | |
backgroundColor: 'rgba(34, 34, 41, 0.02)', | |
backdropFilter: 'blur(100px)', | |
pointerEvents: 'none' // Ensure the overlay doesn't block interactions | |
}}></div> | |
</div> | |
); | |
}; | |
export default AnimatedBackground; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment