Files
chatroom/public/js/effects/rain.js

103 lines
3.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 文件功能:聊天室下雨特效
*
* 使用 Canvas 绘制斜向雨线,模拟真实降雨视觉效果。
* 加粗加深雨线颜色,在浅色背景上清晰可见。
* 特效总时长约 8 秒,结束后自动清理并回调。
*/
const RainEffect = (() => {
// 雨滴类:一条从顶部往下落的斜线
class Drop {
constructor(w, h) {
this.reset(w, h);
}
/**
* 重置/初始化雨滴位置
*/
reset(w, h) {
this.x = Math.random() * w;
this.y = Math.random() * -h;
this.len = Math.random() * 25 + 12; // 雨线长度(稍加长)
this.speed = Math.random() * 10 + 7; // 下落速度(加快)
this.angle = (Math.PI / 180) * (75 + Math.random() * 10);
this.alpha = Math.random() * 0.5 + 0.4; // 提高透明度上限 (0.4-0.9,原 0.2-0.5)
this.strokeW = Math.random() * 1.5 + 0.8; // 线条宽度随机(原 0.8 固定)
this.w = w;
this.h = h;
}
/** 每帧更新雨滴位置 */
update() {
this.x += Math.cos(this.angle) * this.speed * 0.3;
this.y += Math.sin(this.angle) * this.speed;
if (this.y > this.h + this.len) {
this.reset(this.w, this.h);
}
}
/** 绘制雨滴线段(加深蓝色,在浅色背景上更明显) */
draw(ctx) {
ctx.save();
ctx.strokeStyle = `rgba(50, 130, 220, ${this.alpha})`; // 加深蓝色(原浅蓝 155,200,255
ctx.lineWidth = this.strokeW;
ctx.shadowColor = "rgba(30, 100, 200, 0.4)";
ctx.shadowBlur = 2;
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(
this.x + Math.cos(this.angle) * this.len,
this.y + Math.sin(this.angle) * this.len,
);
ctx.stroke();
ctx.restore();
}
}
/**
* 启动下雨特效
*
* @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 = 8000;
const DROP_COUNT = 200; // 增加雨滴数量(原 180
const drops = Array.from({ length: DROP_COUNT }, () => {
const d = new Drop(w, h);
d.y = Math.random() * h;
return d;
});
let animId = null;
const startTime = performance.now();
function animate(now) {
// 清除画布(透明,不遮挡聊天背景)
ctx.clearRect(0, 0, w, h);
drops.forEach((d) => {
d.update();
d.draw(ctx);
});
if (now - startTime < DURATION) {
animId = requestAnimationFrame(animate);
} else {
cancelAnimationFrame(animId);
ctx.clearRect(0, 0, w, h);
onEnd();
}
}
animId = requestAnimationFrame(animate);
}
return { start };
})();