Add new chat effects and shop items
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* 文件功能:聊天室萤火虫特效
|
||||
*
|
||||
* 使用高亮柔光粒子模拟夜色中的萤火虫,让光点在屏幕中缓慢游走、
|
||||
* 呼吸闪烁,适合常驻氛围和安静主题房间。
|
||||
*/
|
||||
|
||||
const FirefliesEffect = (() => {
|
||||
class Firefly {
|
||||
constructor(w, h) {
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
this.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置萤火虫位置与呼吸参数。
|
||||
*/
|
||||
reset() {
|
||||
this.x = Math.random() * this.w;
|
||||
this.y = Math.random() * this.h;
|
||||
this.baseRadius = Math.random() * 3 + 2.4;
|
||||
this.alpha = Math.random() * 0.24 + 0.36;
|
||||
this.phase = Math.random() * Math.PI * 2;
|
||||
this.phaseSpeed = Math.random() * 0.05 + 0.015;
|
||||
this.angle = Math.random() * Math.PI * 2;
|
||||
this.speed = Math.random() * 0.55 + 0.22;
|
||||
this.flutter = Math.random() * Math.PI * 2;
|
||||
this.flutterSpeed = Math.random() * 0.18 + 0.08;
|
||||
this.trail = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新萤火虫轨迹。
|
||||
*/
|
||||
update() {
|
||||
this.phase += this.phaseSpeed;
|
||||
this.flutter += this.flutterSpeed;
|
||||
this.trail.push({ x: this.x, y: this.y });
|
||||
if (this.trail.length > 7) {
|
||||
this.trail.shift();
|
||||
}
|
||||
|
||||
this.angle += (Math.random() - 0.5) * 0.08 + Math.sin(this.flutter) * 0.012;
|
||||
this.x += Math.cos(this.angle) * this.speed;
|
||||
this.y += Math.sin(this.angle) * this.speed;
|
||||
|
||||
if (this.x < -20) this.x = this.w + 20;
|
||||
if (this.x > this.w + 20) this.x = -20;
|
||||
if (this.y < -20) this.y = this.h + 20;
|
||||
if (this.y > this.h + 20) this.y = -20;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制发光萤火虫。
|
||||
*
|
||||
* @param {CanvasRenderingContext2D} ctx
|
||||
*/
|
||||
draw(ctx) {
|
||||
const pulse = 0.38 + (Math.sin(this.phase) + 1) * 0.42;
|
||||
const radius = this.baseRadius * pulse;
|
||||
const wingSwing = Math.sin(this.flutter) * 1.9;
|
||||
|
||||
this.trail.forEach((point, index) => {
|
||||
const alpha = ((index + 1) / this.trail.length) * 0.08;
|
||||
ctx.save();
|
||||
ctx.fillStyle = `rgba(250, 204, 21, ${alpha})`;
|
||||
ctx.shadowColor = "rgba(250, 204, 21, 0.45)";
|
||||
ctx.shadowBlur = 6;
|
||||
ctx.beginPath();
|
||||
ctx.arc(point.x, point.y, Math.max(0.8, radius * 0.55), 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
});
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(this.x, this.y);
|
||||
ctx.rotate(this.angle);
|
||||
|
||||
// 先画半透明翅膀,再画虫身和发光尾部,让画面更像“萤火虫”而不是单纯光点。
|
||||
ctx.globalAlpha = 0.18 + pulse * 0.08;
|
||||
ctx.fillStyle = "#f8fafc";
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(-radius * 1.15, -radius * 0.45, radius * 1.05, radius * 0.55, wingSwing * 0.05, 0, Math.PI * 2);
|
||||
ctx.ellipse(-radius * 0.1, radius * 0.45, radius * 1.05, radius * 0.55, -wingSwing * 0.05, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.globalAlpha = this.alpha + pulse * 0.3;
|
||||
ctx.fillStyle = "#fde047";
|
||||
ctx.shadowColor = "rgba(250, 204, 21, 0.85)";
|
||||
ctx.shadowBlur = 18 + pulse * 12;
|
||||
ctx.beginPath();
|
||||
ctx.arc(radius * 0.75, 0, radius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = "rgba(51, 65, 85, 0.9)";
|
||||
ctx.shadowBlur = 0;
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(-radius * 0.25, 0, radius * 1.05, Math.max(1.4, radius * 0.5), 0, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.strokeStyle = "rgba(248, 250, 252, 0.35)";
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-radius * 1.1, -radius * 0.2);
|
||||
ctx.lineTo(-radius * 1.65, -radius * 0.8);
|
||||
ctx.moveTo(-radius * 1.1, radius * 0.2);
|
||||
ctx.lineTo(-radius * 1.65, radius * 0.8);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动萤火虫特效。
|
||||
*
|
||||
* @param {HTMLCanvasElement} canvas
|
||||
* @param {Function} onEnd
|
||||
*/
|
||||
function start(canvas, onEnd) {
|
||||
const ctx = canvas.getContext("2d");
|
||||
const w = canvas.width;
|
||||
const h = canvas.height;
|
||||
const DURATION = 10500;
|
||||
const COUNT = Math.min(52, Math.max(24, Math.floor(w / 34)));
|
||||
const fireflies = Array.from({ length: COUNT }, () => new Firefly(w, h));
|
||||
const startTime = performance.now();
|
||||
let animId = null;
|
||||
|
||||
function animate(now) {
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
|
||||
fireflies.forEach((firefly) => {
|
||||
firefly.update();
|
||||
firefly.draw(ctx);
|
||||
});
|
||||
|
||||
if (now - startTime < DURATION) {
|
||||
animId = requestAnimationFrame(animate);
|
||||
} else {
|
||||
cancelAnimationFrame(animId);
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
onEnd();
|
||||
}
|
||||
}
|
||||
|
||||
animId = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
return { start };
|
||||
})();
|
||||
Reference in New Issue
Block a user