Fix: 雪花改为深蓝轮廓+白色主体双遍绘制,浅色背景下清晰可辨
This commit is contained in:
+52
-54
@@ -2,70 +2,76 @@
|
|||||||
* 文件功能:聊天室下雪特效
|
* 文件功能:聊天室下雪特效
|
||||||
*
|
*
|
||||||
* 使用 Canvas 绘制真实六角雪花图案(6条主臂 + 左右分叉)。
|
* 使用 Canvas 绘制真实六角雪花图案(6条主臂 + 左右分叉)。
|
||||||
* 雪花大小、速度、旋转角度随机,自然飘落效果。
|
* 采用深色描边+白色主体双遍绘制,在任何背景颜色上都清晰可见。
|
||||||
* 特效总时长约 10 秒,结束后自动清理并回调。
|
* 特效总时长约 10 秒,结束后自动清理并回调。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const SnowEffect = (() => {
|
const SnowEffect = (() => {
|
||||||
/**
|
/**
|
||||||
* 在指定位置绘制一朵六角雪花
|
* 在指定位置绘制一朵六角雪花(深色轮廓 + 白色主体)
|
||||||
*
|
*
|
||||||
* @param {CanvasRenderingContext2D} ctx
|
* @param {CanvasRenderingContext2D} ctx
|
||||||
* @param {number} x 中心 x
|
* @param {number} x 中心 x
|
||||||
* @param {number} y 中心 y
|
* @param {number} y 中心 y
|
||||||
* @param {number} r 主臂长度(半径)
|
* @param {number} r 主臂长度
|
||||||
* @param {number} alpha 透明度
|
* @param {number} alpha 透明度
|
||||||
* @param {number} rot 旋转角度(弧度)
|
* @param {number} rot 旋转角度(弧度)
|
||||||
*/
|
*/
|
||||||
function _drawFlake(ctx, x, y, r, alpha, rot) {
|
function _drawFlake(ctx, x, y, r, alpha, rot) {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.globalAlpha = alpha;
|
ctx.globalAlpha = alpha;
|
||||||
ctx.strokeStyle = "#ffffff";
|
|
||||||
ctx.lineWidth = Math.max(1, r * 0.12);
|
|
||||||
ctx.shadowColor = "rgba(180, 210, 255, 0.9)";
|
|
||||||
ctx.shadowBlur = 4;
|
|
||||||
ctx.lineCap = "round";
|
ctx.lineCap = "round";
|
||||||
|
|
||||||
ctx.translate(x, y);
|
ctx.translate(x, y);
|
||||||
ctx.rotate(rot);
|
ctx.rotate(rot);
|
||||||
|
|
||||||
// 绘制 6 条主臂(每 60° 一条)
|
// 两遍绘制:先深蓝色粗描边,再白色细线覆盖
|
||||||
for (let i = 0; i < 6; i++) {
|
// 这样在浅蓝、白色等背景上都清晰可辨
|
||||||
ctx.save();
|
const passes = [
|
||||||
ctx.rotate((Math.PI / 3) * i);
|
{ color: "rgba(30, 60, 140, 0.8)", lw: r * 0.22 + 2.5 }, // 深蓝粗描边
|
||||||
|
{ color: "rgba(255, 255, 255, 1.0)", lw: Math.max(1, r * 0.11) }, // 白色主体
|
||||||
|
];
|
||||||
|
|
||||||
// 主臂
|
passes.forEach(({ color, lw }) => {
|
||||||
ctx.beginPath();
|
ctx.strokeStyle = color;
|
||||||
ctx.moveTo(0, 0);
|
ctx.lineWidth = lw;
|
||||||
ctx.lineTo(r, 0);
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
// 主臂上的两段斜向分叉(在 0.4r 和 0.65r 处)
|
for (let i = 0; i < 6; i++) {
|
||||||
const branchLen = r * 0.35;
|
ctx.save();
|
||||||
const branchAngle = Math.PI / 4; // 45°
|
ctx.rotate((Math.PI / 3) * i);
|
||||||
|
|
||||||
[0.4, 0.65].forEach((pos) => {
|
// 主臂
|
||||||
const bx = r * pos;
|
|
||||||
// 上分叉
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(bx, 0);
|
ctx.moveTo(0, 0);
|
||||||
ctx.lineTo(
|
ctx.lineTo(r, 0);
|
||||||
bx + Math.cos(branchAngle) * branchLen,
|
|
||||||
Math.sin(branchAngle) * branchLen,
|
|
||||||
);
|
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
// 下分叉(对称)
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(bx, 0);
|
|
||||||
ctx.lineTo(
|
|
||||||
bx + Math.cos(branchAngle) * branchLen,
|
|
||||||
-Math.sin(branchAngle) * branchLen,
|
|
||||||
);
|
|
||||||
ctx.stroke();
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.restore();
|
// 斜向分叉(0.4r 和 0.65r 处各一对)
|
||||||
}
|
const branchLen = r * 0.35;
|
||||||
|
const branchAngle = Math.PI / 4; // 45°
|
||||||
|
|
||||||
|
[0.4, 0.65].forEach((pos) => {
|
||||||
|
const bx = r * pos;
|
||||||
|
// 上分叉
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(bx, 0);
|
||||||
|
ctx.lineTo(
|
||||||
|
bx + Math.cos(branchAngle) * branchLen,
|
||||||
|
Math.sin(branchAngle) * branchLen,
|
||||||
|
);
|
||||||
|
ctx.stroke();
|
||||||
|
// 下分叉
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(bx, 0);
|
||||||
|
ctx.lineTo(
|
||||||
|
bx + Math.cos(branchAngle) * branchLen,
|
||||||
|
-Math.sin(branchAngle) * branchLen,
|
||||||
|
);
|
||||||
|
ctx.stroke();
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
@@ -78,25 +84,19 @@ const SnowEffect = (() => {
|
|||||||
this.reset(true);
|
this.reset(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置雪花位置
|
|
||||||
*
|
|
||||||
* @param {boolean} initial 初始化时 Y 随机分布全屏
|
|
||||||
*/
|
|
||||||
reset(initial = false) {
|
reset(initial = false) {
|
||||||
this.x = Math.random() * this.w;
|
this.x = Math.random() * this.w;
|
||||||
this.y = initial ? Math.random() * this.h : -20;
|
this.y = initial ? Math.random() * this.h : -20;
|
||||||
this.r = Math.random() * 10 + 6; // 主臂长度 6-16px
|
this.r = Math.random() * 10 + 7; // 主臂长度 7-17px(略放大)
|
||||||
this.speed = Math.random() * 1.2 + 0.4; // 下落速度(慢慢飘)
|
this.speed = Math.random() * 1.2 + 0.4;
|
||||||
this.drift = Math.random() * 0.6 - 0.3; // 水平漂移
|
this.drift = Math.random() * 0.6 - 0.3;
|
||||||
this.alpha = Math.random() * 0.3 + 0.7; // 透明度 0.7-1.0
|
this.alpha = Math.random() * 0.3 + 0.7; // 透明度 0.7-1.0
|
||||||
this.rot = Math.random() * Math.PI * 2; // 初始旋转角
|
this.rot = Math.random() * Math.PI * 2;
|
||||||
this.rotSpd = (Math.random() - 0.5) * 0.02; // 旋转速度
|
this.rotSpd = (Math.random() - 0.5) * 0.02;
|
||||||
this.wobble = 0;
|
this.wobble = 0;
|
||||||
this.wobSpd = Math.random() * 0.03 + 0.01; // 摇摆频率
|
this.wobSpd = Math.random() * 0.03 + 0.01;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 每帧更新 */
|
|
||||||
update() {
|
update() {
|
||||||
this.wobble += this.wobSpd;
|
this.wobble += this.wobSpd;
|
||||||
this.x += Math.sin(this.wobble) * 0.5 + this.drift;
|
this.x += Math.sin(this.wobble) * 0.5 + this.drift;
|
||||||
@@ -107,7 +107,6 @@ const SnowEffect = (() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 绘制雪花 */
|
|
||||||
draw(ctx) {
|
draw(ctx) {
|
||||||
_drawFlake(ctx, this.x, this.y, this.r, this.alpha, this.rot);
|
_drawFlake(ctx, this.x, this.y, this.r, this.alpha, this.rot);
|
||||||
}
|
}
|
||||||
@@ -124,9 +123,8 @@ const SnowEffect = (() => {
|
|||||||
const w = canvas.width;
|
const w = canvas.width;
|
||||||
const h = canvas.height;
|
const h = canvas.height;
|
||||||
const DURATION = 10000;
|
const DURATION = 10000;
|
||||||
const FLAKE_COUNT = 80; // 六角雪花绘制开销较大,80 个足够
|
const FLAKE_COUNT = 80;
|
||||||
|
|
||||||
// 初始化所有雪花,随机分布全屏
|
|
||||||
const flakes = Array.from(
|
const flakes = Array.from(
|
||||||
{ length: FLAKE_COUNT },
|
{ length: FLAKE_COUNT },
|
||||||
() => new Flake(w, h),
|
() => new Flake(w, h),
|
||||||
|
|||||||
Reference in New Issue
Block a user