Feat: 新增下雪特效,加强烟花/下雨在浅色背景的显色(发光粒子+深色雨线)

This commit is contained in:
2026-02-27 14:22:13 +08:00
parent 215fbd7221
commit 4da2d19b1f
7 changed files with 166 additions and 54 deletions
+31 -34
View File
@@ -2,7 +2,8 @@
* 文件功能:聊天室烟花特效
*
* 使用 Canvas 粒子系统在全屏播放多发烟花爆炸动画。
* 特效总时长约 4 秒,结束后自动清理并回调
* 粒子加大、加发光描边,在浅色背景上也清晰可见
* 特效总时长约 5 秒,结束后自动清理并回调。
*/
const FireworksEffect = (() => {
@@ -14,13 +15,13 @@ const FireworksEffect = (() => {
this.color = color;
// 随机方向和速度
const angle = Math.random() * Math.PI * 2;
const speed = Math.random() * 6 + 2;
const speed = Math.random() * 7 + 3;
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;
this.decay = Math.random() * 0.01 + 0.01; // 衰减略慢,显色更久
this.radius = Math.random() * 4 + 2; // 增大粒子半径
}
/** 每帧更新粒子位置和状态 */
@@ -28,16 +29,18 @@ const FireworksEffect = (() => {
this.vy += this.gravity;
this.x += this.vx;
this.y += this.vy;
this.vx *= 0.98; // 空气阻力
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.shadowColor = this.color;
ctx.shadowBlur = 8;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fill();
@@ -45,29 +48,24 @@ const FireworksEffect = (() => {
}
}
// 预定义烟花颜色组
// 预定义烟花颜色组(饱和度高,避免和浅蓝背景撞色)
const COLORS = [
"#ff4444",
"#ff8800",
"#ffdd00",
"#44ff44",
"#44ddff",
"#8844ff",
"#ff44cc",
"#ffffff",
"#ffaaaa",
"#aaffaa",
"#aaaaff",
"#ffffaa",
"#ff2200",
"#ff7700",
"#ffcc00",
"#00cc33",
"#cc00ff",
"#ff0088",
"#00aaff",
"#ff4488",
"#ff6600",
"#aaff00",
"#ff2255",
"#ffaa00",
];
/**
* 发射一枚烟花,返回粒子数组
*
* @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)];
@@ -88,12 +86,12 @@ const FireworksEffect = (() => {
const ctx = canvas.getContext("2d");
const w = canvas.width;
const h = canvas.height;
const DURATION = 4500; // 总时长(ms
const DURATION = 5000; // 总时长(ms
let particles = [];
let animId = null;
let launchCount = 0;
const MAX_LAUNCHES = 8; // 总发射几枚烟花
const MAX_LAUNCHES = 10; // 总发射枚数(增加)
// 定时发射烟花
const launchInterval = setInterval(() => {
@@ -101,21 +99,21 @@ const FireworksEffect = (() => {
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));
const x = w * (0.1 + Math.random() * 0.8);
const y = h * (0.05 + Math.random() * 0.5);
const cnt = Math.floor(Math.random() * 50) + 80; // 每枚 80-130 粒子(增多)
particles = particles.concat(_burst(x, y, cnt));
launchCount++;
}, 500);
}, 450);
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();
@@ -125,7 +123,6 @@ const FireworksEffect = (() => {
if (now - startTime < DURATION) {
animId = requestAnimationFrame(animate);
} else {
// 特效结束:清空 canvas 后回调
clearInterval(launchInterval);
cancelAnimationFrame(animId);
ctx.clearRect(0, 0, w, h);