/** * 文件功能:聊天室烟花特效 * * 使用 Canvas 粒子系统在全屏播放多发烟花爆炸动画。 * 特效总时长约 4 秒,结束后自动清理并回调。 */ const FireworksEffect = (() => { // 粒子类:模拟一个爆炸后的发光粒子 class Particle { constructor(x, y, color) { this.x = x; this.y = y; this.color = color; // 随机方向和速度 const angle = Math.random() * Math.PI * 2; const speed = Math.random() * 6 + 2; this.vx = Math.cos(angle) * speed; this.vy = Math.sin(angle) * speed; this.alpha = 1; this.gravity = 0.12; this.decay = Math.random() * 0.012 + 0.012; // 透明度每帧衰减量 this.radius = Math.random() * 3 + 1; } /** 每帧更新粒子位置和状态 */ update() { this.vy += this.gravity; this.x += this.vx; this.y += this.vy; this.vx *= 0.98; // 空气阻力 this.vy *= 0.98; this.alpha -= this.decay; } /** 绘制粒子 */ draw(ctx) { ctx.save(); ctx.globalAlpha = Math.max(0, this.alpha); ctx.fillStyle = this.color; ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); ctx.fill(); ctx.restore(); } } // 预定义烟花颜色组 const COLORS = [ "#ff4444", "#ff8800", "#ffdd00", "#44ff44", "#44ddff", "#8844ff", "#ff44cc", "#ffffff", "#ffaaaa", "#aaffaa", "#aaaaff", "#ffffaa", ]; /** * 发射一枚烟花,返回粒子数组 * * @param {number} x 爆炸中心 x * @param {number} y 爆炸中心 y * @param {number} count 粒子数量 * @returns {Particle[]} */ function _burst(x, y, count) { const color = COLORS[Math.floor(Math.random() * COLORS.length)]; const particles = []; for (let i = 0; i < count; i++) { particles.push(new Particle(x, y, color)); } return particles; } /** * 启动烟花特效 * * @param {HTMLCanvasElement} canvas 全屏 Canvas * @param {Function} onEnd 特效结束回调 */ function start(canvas, onEnd) { const ctx = canvas.getContext("2d"); const w = canvas.width; const h = canvas.height; const DURATION = 4500; // 总时长(ms) let particles = []; let animId = null; let launchCount = 0; const MAX_LAUNCHES = 8; // 总共发射几枚烟花 // 定时发射烟花 const launchInterval = setInterval(() => { if (launchCount >= MAX_LAUNCHES) { clearInterval(launchInterval); return; } const x = w * (0.15 + Math.random() * 0.7); // 避免贴近边缘 const y = h * (0.1 + Math.random() * 0.5); // 在屏幕上半区爆炸 const count = Math.floor(Math.random() * 40) + 60; particles = particles.concat(_burst(x, y, count)); launchCount++; }, 500); const startTime = performance.now(); // 动画循环 function animate(now) { // 清除画布(保持透明,不遮挡聊天背景) ctx.clearRect(0, 0, w, h); // 更新并绘制存活粒子(粒子自带 alpha 衰减,视觉上有淡出效果) particles = particles.filter((p) => p.alpha > 0.02); particles.forEach((p) => { p.update(); p.draw(ctx); }); if (now - startTime < DURATION) { animId = requestAnimationFrame(animate); } else { // 特效结束:清空 canvas 后回调 clearInterval(launchInterval); cancelAnimationFrame(animId); ctx.clearRect(0, 0, w, h); onEnd(); } } animId = requestAnimationFrame(animate); } return { start }; })();