Add new chat effects and shop items

This commit is contained in:
2026-04-12 16:48:58 +08:00
parent 33a3e5d118
commit 70cb170f2c
25 changed files with 1707 additions and 60 deletions
+97 -16
View File
@@ -1,9 +1,8 @@
/**
* 文件功能:聊天室下雪特效
*
* 使用 Canvas 绘制真实六角雪花图案(6条主臂 + 左右分叉)。
* 采用深色描边+白色主体双遍绘制,在任何背景颜色上都清晰可见
* 特效总时长约 10 秒,结束后自动清理并回调。
* 使用 Canvas 同时绘制远景小雪与近景六角雪花,
* 通过层次、大小、速度差营造更饱满的飘雪效果
*/
const SnowEffect = (() => {
@@ -76,30 +75,64 @@ const SnowEffect = (() => {
ctx.restore();
}
/**
* 绘制远景小雪点,让画面更密实,不会只看到零散大雪花。
*
* @param {CanvasRenderingContext2D} ctx
* @param {number} x
* @param {number} y
* @param {number} radius
* @param {number} alpha
*/
function _drawSoftSnow(ctx, x, y, radius, alpha) {
ctx.save();
ctx.globalAlpha = alpha;
ctx.fillStyle = "rgba(255,255,255,0.95)";
ctx.shadowColor = "rgba(255,255,255,0.8)";
ctx.shadowBlur = 8;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
// 雪花粒子类
class Flake {
constructor(w, h) {
constructor(w, h, layer = "front") {
this.w = w;
this.h = h;
this.layer = layer;
this.reset(true);
}
/**
* 重置雪花,让前景和背景使用不同参数。
*
* @param {boolean} initial
*/
reset(initial = false) {
this.x = Math.random() * this.w;
this.y = initial ? Math.random() * this.h : -20;
this.r = Math.random() * 10 + 7; // 主臂长度 7-17px(略放大)
this.speed = Math.random() * 1.2 + 0.4;
this.drift = Math.random() * 0.6 - 0.3;
this.alpha = Math.random() * 0.3 + 0.7; // 透明度 0.7-1.0
if (this.layer === "back") {
this.r = Math.random() * 2 + 1.1;
this.speed = Math.random() * 0.55 + 0.28;
this.drift = Math.random() * 0.28 - 0.14;
this.alpha = Math.random() * 0.25 + 0.28;
} else {
this.r = Math.random() * 9 + 6.5;
this.speed = Math.random() * 1.05 + 0.48;
this.drift = Math.random() * 0.7 - 0.35;
this.alpha = Math.random() * 0.25 + 0.68;
}
this.rot = Math.random() * Math.PI * 2;
this.rotSpd = (Math.random() - 0.5) * 0.02;
this.rotSpd = (Math.random() - 0.5) * (this.layer === "back" ? 0.008 : 0.018);
this.wobble = 0;
this.wobSpd = Math.random() * 0.03 + 0.01;
this.wobSpd = Math.random() * (this.layer === "back" ? 0.02 : 0.028) + 0.008;
}
update() {
this.wobble += this.wobSpd;
this.x += Math.sin(this.wobble) * 0.5 + this.drift;
this.x += Math.sin(this.wobble) * (this.layer === "back" ? 0.28 : 0.58) + this.drift;
this.y += this.speed;
this.rot += this.rotSpd;
if (this.y > this.h + 20) {
@@ -108,6 +141,11 @@ const SnowEffect = (() => {
}
draw(ctx) {
if (this.layer === "back") {
_drawSoftSnow(ctx, this.x, this.y, this.r, this.alpha);
return;
}
_drawFlake(ctx, this.x, this.y, this.r, this.alpha, this.rot);
}
}
@@ -123,12 +161,24 @@ const SnowEffect = (() => {
const w = canvas.width;
const h = canvas.height;
const DURATION = 10000;
const FLAKE_COUNT = 80;
const flakes = [
...Array.from(
{ length: Math.min(120, Math.max(70, Math.floor(w / 18))) },
() => new Flake(w, h, "back"),
),
...Array.from(
{ length: Math.min(64, Math.max(34, Math.floor(w / 42))) },
() => new Flake(w, h, "front"),
),
];
const flakes = Array.from(
{ length: FLAKE_COUNT },
() => new Flake(w, h),
);
const breezeBands = Array.from({ length: 2 }, () => ({
x: Math.random() * w,
y: Math.random() * h,
radius: 180 + Math.random() * 140,
alpha: Math.random() * 0.05 + 0.025,
drift: Math.random() * 0.3 + 0.08,
}));
let animId = null;
const startTime = performance.now();
@@ -136,6 +186,37 @@ const SnowEffect = (() => {
function animate(now) {
ctx.clearRect(0, 0, w, h);
// 加一层极淡的冷白雾感,让雪景更有氛围但不遮挡聊天内容。
const mist = ctx.createLinearGradient(0, 0, 0, h);
mist.addColorStop(0, "rgba(226,240,255,0.08)");
mist.addColorStop(0.4, "rgba(226,240,255,0.03)");
mist.addColorStop(1, "rgba(226,240,255,0)");
ctx.fillStyle = mist;
ctx.fillRect(0, 0, w, h);
breezeBands.forEach((band) => {
band.x += band.drift;
if (band.x - band.radius > w) {
band.x = -band.radius;
band.y = Math.random() * h;
}
const breeze = ctx.createRadialGradient(
band.x,
band.y,
0,
band.x,
band.y,
band.radius,
);
breeze.addColorStop(0, `rgba(255,255,255,${band.alpha})`);
breeze.addColorStop(1, "rgba(255,255,255,0)");
ctx.fillStyle = breeze;
ctx.beginPath();
ctx.arc(band.x, band.y, band.radius, 0, Math.PI * 2);
ctx.fill();
});
flakes.forEach((f) => {
f.update();
f.draw(ctx);