import { Canvas, View } from '@tarojs/components' import Taro, { useReady, useUnload } from '@tarojs/taro' import { useRef } from 'react' import './index.scss' export default function ParticleBackground() { const canvasRef = useRef(null) const animationRef = useRef(null) useReady(() => { const query = Taro.createSelectorQuery() query.select('#particle-canvas') .fields({ node: true, size: true }) .exec((res) => { if (!res[0]) return const canvas = res[0].node const ctx = canvas.getContext('2d') const dpr = Taro.getSystemInfoSync().pixelRatio canvas.width = res[0].width * dpr canvas.height = res[0].height * dpr ctx.scale(dpr, dpr) const width = res[0].width const height = res[0].height // Init particles const particles: any[] = [] const particleCount = 40 // Reduced for mobile performance const meteors: any[] = [] const meteorCount = 4 class Particle { x: number y: number vx: number vy: number size: number color: string constructor() { this.x = Math.random() * width this.y = Math.random() * height this.vx = (Math.random() - 0.5) * 0.5 this.vy = (Math.random() - 0.5) * 0.5 this.size = Math.random() * 2 this.color = Math.random() > 0.5 ? 'rgba(0, 185, 107, ' : 'rgba(0, 240, 255, ' } update() { this.x += this.vx this.y += this.vy if (this.x < 0 || this.x > width) this.vx *= -1 if (this.y < 0 || this.y > height) this.vy *= -1 } draw() { ctx.beginPath() ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2) ctx.fillStyle = this.color + (0.5 + Math.random() * 0.5) + ')' ctx.fill() } } class Meteor { x: number y: number vx: number vy: number len: number color: string opacity: number maxOpacity: number wait: number constructor() { this.x = 0 this.y = 0 this.vx = 0 this.vy = 0 this.len = 0 this.color = '' this.opacity = 0 this.maxOpacity = 0 this.wait = 0 this.reset() } reset() { this.x = Math.random() * width * 1.5 this.y = Math.random() * -height this.vx = -(Math.random() * 3 + 3) this.vy = Math.random() * 3 + 3 this.len = Math.random() * 100 + 100 this.color = Math.random() > 0.5 ? 'rgba(0, 185, 107, ' : 'rgba(0, 240, 255, ' this.opacity = 0 this.maxOpacity = Math.random() * 0.5 + 0.2 this.wait = Math.random() * 200 } update() { if (this.wait > 0) { this.wait-- return } this.x += this.vx this.y += this.vy if (this.opacity < this.maxOpacity) this.opacity += 0.02 if (this.x < -this.len || this.y > height + this.len) this.reset() } draw() { if (this.wait > 0) return const tailX = this.x - this.vx * (this.len / 15) const tailY = this.y - this.vy * (this.len / 15) const gradient = ctx.createLinearGradient(this.x, this.y, tailX, tailY) gradient.addColorStop(0, this.color + this.opacity + ')') gradient.addColorStop(1, this.color + '0)') ctx.save() ctx.beginPath() ctx.strokeStyle = gradient ctx.lineWidth = 2 ctx.lineCap = 'round' ctx.moveTo(this.x, this.y) ctx.lineTo(tailX, tailY) ctx.stroke() ctx.restore() } } for (let i = 0; i < particleCount; i++) particles.push(new Particle()) for (let i = 0; i < meteorCount; i++) meteors.push(new Meteor()) const animate = () => { ctx.clearRect(0, 0, width, height) // Draw Meteors meteors.forEach(m => { m.update() m.draw() }) // Draw Lines ctx.lineWidth = 0.5 for (let i = 0; i < particleCount; i++) { for (let j = i; j < particleCount; j++) { const dx = particles[i].x - particles[j].x const dy = particles[i].y - particles[j].y const dist = Math.sqrt(dx * dx + dy * dy) if (dist < 80) { // Reduced distance for mobile ctx.beginPath() ctx.strokeStyle = `rgba(100, 255, 218, ${1 - dist / 80})` ctx.moveTo(particles[i].x, particles[i].y) ctx.lineTo(particles[j].x, particles[j].y) ctx.stroke() } } } // Draw Particles particles.forEach(p => { p.update() p.draw() }) canvas.requestAnimationFrame(animate) } animate() }) }) return ( ) }