mkcart / frontend /src /components /ParticleBackground.jsx
Kumar
updated
c2efbe6
import { useEffect, useRef} from "react"
import { motion } from "framer-motion"
const ParticleBackground = () => {
const canvasRef = useRef(null)
const particlesRef = useRef([])
const mouseRef = useRef({ x: 0, y: 0 })
useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext("2d")
let animationFrameId
const resizeCanvas = () => {
const { innerWidth, innerHeight } = window
canvas.width = innerWidth
canvas.height = innerHeight
particlesRef.current.forEach((particle) => {
if (particle.x > innerWidth) particle.x = innerWidth * 0.9
if (particle.y > innerHeight) particle.y = innerHeight * 0.9
if (particle.x < 0) particle.x = innerWidth * 0.1
if (particle.y < 0) particle.y = innerHeight * 0.1
particle.baseX = Math.min(particle.baseX, innerWidth * 0.9)
particle.baseY = Math.min(particle.baseY, innerHeight * 0.9)
})
}
resizeCanvas()
let resizeTimeout
const debouncedResize = () => {
clearTimeout(resizeTimeout)
resizeTimeout = setTimeout(resizeCanvas, 100)
}
window.addEventListener("resize", debouncedResize)
const handleMouseMove = (e) => {
mouseRef.current = { x: e.clientX, y: e.clientY }
}
window.addEventListener("mousemove", handleMouseMove)
class Particle {
constructor(type = "default") {
this.type = type
this.reset()
this.setupByType()
}
reset() {
this.x = Math.random() * canvas.width
this.y = Math.random() * canvas.height
this.baseX = this.x
this.baseY = this.y
this.density = Math.random() * 30 + 1
this.life = 1
this.maxLife = Math.random() * 100 + 50
}
setupByType() {
const isMobile = window.innerWidth <= 768 || /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
const speedMultiplier = isMobile ? 0.3 : 1
switch (this.type) {
case "cosmic":
this.size = Math.random() * 4 + 2
this.speedX = (Math.random() * 0.3 - 0.15) * speedMultiplier
this.speedY = (Math.random() * 0.3 - 0.15) * speedMultiplier
this.color = this.getCosmicColor()
this.pulseSpeed = Math.random() * 0.015 + 0.008
this.pulse = 0
break
case "nebula":
this.size = Math.random() * 8 + 4
this.speedX = (Math.random() * 0.15 - 0.075) * speedMultiplier
this.speedY = (Math.random() * 0.15 - 0.075) * speedMultiplier
this.color = this.getNebulaColor()
this.opacity = Math.random() * 0.3 + 0.1
break
case "star":
this.size = Math.random() * 2 + 1
this.speedX = (Math.random() * 0.2 - 0.1) * speedMultiplier
this.speedY = (Math.random() * 0.2 - 0.1) * speedMultiplier
this.color = this.getStarColor()
this.twinkle = Math.random() * Math.PI * 2
this.twinkleSpeed = Math.random() * 0.03 + 0.015
break
case "energy":
this.size = Math.random() * 3 + 1
this.speedX = (Math.random() * 0.6 - 0.3) * speedMultiplier
this.speedY = (Math.random() * 0.6 - 0.3) * speedMultiplier
this.color = this.getEnergyColor()
this.trail = []
this.trailLength = 5
break
default:
this.size = Math.random() * 3 + 1
this.speedX = (Math.random() * 1.2 - 0.6) * speedMultiplier
this.speedY = (Math.random() * 1.2 - 0.6) * speedMultiplier
this.color = this.getDefaultColor()
this.opacity = Math.random() * 0.5 + 0.2
}
}
getCosmicColor() {
const colors = [
`rgba(102, 126, 234, ${Math.random() * 0.8 + 0.2})`,
`rgba(246, 211, 101, ${Math.random() * 0.8 + 0.2})`,
`rgba(255, 154, 158, ${Math.random() * 0.8 + 0.2})`,
`rgba(168, 237, 234, ${Math.random() * 0.8 + 0.2})`,
`rgba(254, 207, 239, ${Math.random() * 0.8 + 0.2})`,
]
return colors[Math.floor(Math.random() * colors.length)]
}
getNebulaColor() {
const colors = [
`rgba(120, 119, 198, ${Math.random() * 0.4 + 0.1})`,
`rgba(255, 119, 198, ${Math.random() * 0.4 + 0.1})`,
`rgba(120, 219, 255, ${Math.random() * 0.4 + 0.1})`,
]
return colors[Math.floor(Math.random() * colors.length)]
}
getStarColor() {
const colors = [
`rgba(255, 255, 255, ${Math.random() * 0.9 + 0.1})`,
`rgba(255, 248, 220, ${Math.random() * 0.9 + 0.1})`,
`rgba(173, 216, 230, ${Math.random() * 0.9 + 0.1})`,
]
return colors[Math.floor(Math.random() * colors.length)]
}
getEnergyColor() {
const colors = [
`rgba(0, 255, 255, ${Math.random() * 0.8 + 0.2})`,
`rgba(255, 0, 255, ${Math.random() * 0.8 + 0.2})`,
`rgba(255, 255, 0, ${Math.random() * 0.8 + 0.2})`,
]
return colors[Math.floor(Math.random() * colors.length)]
}
getDefaultColor() {
const colors = [
`rgba(102, 126, 234, ${Math.random() * 0.6 + 0.2})`,
`rgba(246, 211, 101, ${Math.random() * 0.6 + 0.2})`,
`rgba(255, 154, 158, ${Math.random() * 0.6 + 0.2})`,
]
return colors[Math.floor(Math.random() * colors.length)]
}
update() {
const mouse = mouseRef.current
const dx = mouse.x - this.x
const dy = mouse.y - this.y
const distance = Math.sqrt(dx * dx + dy * dy)
if (distance > 0 && distance < 150 && window.innerWidth > 768) {
const forceDirectionX = dx / distance
const forceDirectionY = dy / distance
const maxDistance = 150
const force = (maxDistance - distance) / maxDistance
const directionX = forceDirectionX * force * this.density * 0.3
const directionY = forceDirectionY * force * this.density * 0.3
this.x -= directionX
this.y -= directionY
} else {
if (this.x !== this.baseX) {
const dx = this.x - this.baseX
this.x -= dx / 15
}
if (this.y !== this.baseY) {
const dy = this.y - this.baseY
this.y -= dy / 15
}
}
switch (this.type) {
case "cosmic":
this.pulse += this.pulseSpeed
this.x += this.speedX
this.y += this.speedY
break
case "nebula":
this.x += this.speedX
this.y += this.speedY
this.opacity = Math.sin(Date.now() * 0.001 + this.x * 0.01) * 0.2 + 0.3
break
case "star":
this.twinkle += this.twinkleSpeed
this.x += this.speedX
this.y += this.speedY
break
case "energy":
this.trail.push({ x: this.x, y: this.y })
if (this.trail.length > this.trailLength) {
this.trail.shift()
}
this.x += this.speedX
this.y += this.speedY
break
default:
this.x += this.speedX
this.y += this.speedY
}
if (this.x > canvas.width) this.x = 0
if (this.x < 0) this.x = canvas.width
if (this.y > canvas.height) this.y = 0
if (this.y < 0) this.y = canvas.height
this.life++
if (this.life > this.maxLife) {
this.reset()
this.setupByType()
}
}
draw() {
ctx.save()
switch (this.type) {
case "cosmic":
this.drawCosmic()
break
case "nebula":
this.drawNebula()
break
case "star":
this.drawStar()
break
case "energy":
this.drawEnergy()
break
default:
this.drawDefault()
}
ctx.restore()
}
drawCosmic() {
const pulseSize = this.size + Math.sin(this.pulse) * 2
const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, pulseSize * 3)
gradient.addColorStop(0, this.color)
gradient.addColorStop(1, "transparent")
ctx.fillStyle = gradient
ctx.beginPath()
ctx.arc(this.x, this.y, pulseSize * 3, 0, Math.PI * 2)
ctx.fill()
ctx.fillStyle = this.color
ctx.beginPath()
ctx.arc(this.x, this.y, pulseSize, 0, Math.PI * 2)
ctx.fill()
}
drawNebula() {
ctx.globalAlpha = this.opacity
const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.size * 4)
gradient.addColorStop(0, this.color)
gradient.addColorStop(0.5, this.color.replace(/[\d.]+\)$/g, "0.1)"))
gradient.addColorStop(1, "transparent")
ctx.fillStyle = gradient
ctx.beginPath()
ctx.arc(this.x, this.y, this.size * 4, 0, Math.PI * 2)
ctx.fill()
ctx.globalAlpha = 1
}
drawStar() {
const twinkleOpacity = Math.sin(this.twinkle) * 0.5 + 0.5
ctx.globalAlpha = twinkleOpacity
ctx.strokeStyle = this.color
ctx.lineWidth = 1
ctx.beginPath()
ctx.moveTo(this.x - this.size * 2, this.y)
ctx.lineTo(this.x + this.size * 2, this.y)
ctx.moveTo(this.x, this.y - this.size * 2)
ctx.lineTo(this.x, this.y + this.size * 2)
ctx.stroke()
ctx.fillStyle = this.color
ctx.beginPath()
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2)
ctx.fill()
ctx.globalAlpha = 1
}
drawEnergy() {
if (this.trail.length > 1) {
ctx.strokeStyle = this.color
ctx.lineWidth = 2
ctx.beginPath()
ctx.moveTo(this.trail[0].x, this.trail[0].y)
for (let i = 1; i < this.trail.length; i++) {
ctx.globalAlpha = i / this.trail.length
ctx.lineTo(this.trail[i].x, this.trail[i].y)
}
ctx.stroke()
}
ctx.globalAlpha = 1
ctx.fillStyle = this.color
ctx.beginPath()
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2)
ctx.fill()
const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.size * 3)
gradient.addColorStop(0, this.color)
gradient.addColorStop(1, "transparent")
ctx.fillStyle = gradient
ctx.beginPath()
ctx.arc(this.x, this.y, this.size * 3, 0, Math.PI * 2)
ctx.fill()
}
drawDefault() {
ctx.globalAlpha = this.opacity
const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.size * 2)
gradient.addColorStop(0, this.color)
gradient.addColorStop(1, "transparent")
ctx.fillStyle = gradient
ctx.beginPath()
ctx.arc(this.x, this.y, this.size * 2, 0, Math.PI * 2)
ctx.fill()
ctx.fillStyle = this.color
ctx.beginPath()
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2)
ctx.fill()
ctx.globalAlpha = 1
}
}
const createParticles = () => {
const particles = []
const particleCount = Math.min(150, Math.floor((canvas.width * canvas.height) / 15000))
const types = [
{ type: "cosmic", count: Math.floor(particleCount * 0.3) },
{ type: "nebula", count: Math.floor(particleCount * 0.2) },
{ type: "star", count: Math.floor(particleCount * 0.25) },
{ type: "energy", count: Math.floor(particleCount * 0.15) },
{ type: "default", count: Math.floor(particleCount * 0.1) },
]
types.forEach(({ type, count }) => {
for (let i = 0; i < count; i++) {
particles.push(new Particle(type))
}
})
return particles
}
particlesRef.current = createParticles()
const drawConnections = () => {
const particles = particlesRef.current
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const dx = particles[i].x - particles[j].x
const dy = particles[i].y - particles[j].y
const distance = Math.sqrt(dx * dx + dy * dy)
if (distance < 120) {
ctx.globalAlpha = ((120 - distance) / 120) * 0.1
ctx.strokeStyle = "#667eea"
ctx.lineWidth = 1
ctx.beginPath()
ctx.moveTo(particles[i].x, particles[i].y)
ctx.lineTo(particles[j].x, particles[j].y)
ctx.stroke()
}
}
}
ctx.globalAlpha = 1
}
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height)
const bgGradient = ctx.createRadialGradient(
canvas.width / 2,
canvas.height / 2,
0,
canvas.width / 2,
canvas.height / 2,
Math.max(canvas.width, canvas.height),
)
bgGradient.addColorStop(0, "rgba(26, 26, 46, 0.1)")
bgGradient.addColorStop(0.5, "rgba(22, 33, 62, 0.05)")
bgGradient.addColorStop(1, "rgba(15, 52, 96, 0.02)")
ctx.fillStyle = bgGradient
ctx.fillRect(0, 0, canvas.width, canvas.height)
particlesRef.current.forEach((particle) => {
particle.update()
particle.draw()
})
drawConnections()
animationFrameId = requestAnimationFrame(animate)
}
animate()
return () => {
window.removeEventListener("resize", debouncedResize)
window.removeEventListener("mousemove", handleMouseMove)
cancelAnimationFrame(animationFrameId)
clearTimeout(resizeTimeout)
}
}, [])
return (
<>
<canvas
ref={canvasRef}
style={{
position: "fixed",
top: 0,
left: 0,
width: "100%",
height: "100%",
zIndex: -2,
pointerEvents: "none",
}}
/>
{}
<div className="cosmic-overlay">
{}
{[...Array(8)].map((_, i) => (
<motion.div
key={i}
className="cosmic-orb"
style={{
position: "fixed",
width: `${60 + Math.random() * 40}px`,
height: `${60 + Math.random() * 40}px`,
left: `${Math.random() * 100}%`,
top: `${Math.random() * 100}%`,
background: `radial-gradient(circle, ${
["rgba(102, 126, 234, 0.1)", "rgba(246, 211, 101, 0.1)", "rgba(255, 154, 158, 0.1)"][i % 3]
}, transparent)`,
borderRadius: "50%",
filter: "blur(20px)",
pointerEvents: "none",
zIndex: -1,
}}
animate={{
x: [-20, 20, -20],
y: [-30, 30, -30],
scale: [1, 1.2, 1],
opacity: [0.3, 0.7, 0.3],
}}
transition={{
duration: 8 + Math.random() * 4,
repeat: Number.POSITIVE_INFINITY,
delay: i * 0.5,
}}
/>
))}
{}
{[...Array(4)].map((_, i) => (
<motion.div
key={`nebula-${i}`}
className="nebula-effect"
style={{
position: "fixed",
width: `${200 + Math.random() * 200}px`,
height: `${200 + Math.random() * 200}px`,
left: `${Math.random() * 100}%`,
top: `${Math.random() * 100}%`,
background: `radial-gradient(ellipse, ${
["rgba(50, 130, 183, 0.05)", "rgba(160, 44, 112, 0.05)", "rgba(42, 124, 154, 0.05)"][i % 3]
}, transparent)`,
borderRadius: "50%",
filter: "blur(40px)",
pointerEvents: "none",
zIndex: -1,
}}
animate={{
x: [-50, 50, -50],
y: [-40, 40, -40],
rotate: [0, 360],
scale: [0.8, 1.2, 0.8],
opacity: [0.2, 0.5, 0.2],
}}
transition={{
duration: 15 + Math.random() * 10,
repeat: Number.POSITIVE_INFINITY,
delay: i * 2,
}}
/>
))}
</div>
</>
)
}
export default ParticleBackground