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
+91 -25
View File
@@ -1,8 +1,8 @@
/**
* 文件功能:聊天室雷电特效
*
* 使用递归分叉算法在 Canvas 上绘制真实感闪电路径
* 配合全屏闪白效果模拟雷闪。总持续约 5 秒,结束后回调
* 使用递归分叉算法叠加云层闪光、主闪电、余辉残影
* 在聊天室中模拟更有压迫感的雷暴闪电效果
*/
const LightningEffect = (() => {
@@ -51,10 +51,41 @@ const LightningEffect = (() => {
}
/**
* 渲染一次闪电 + 闪屏效果
* 绘制顶部乌云压光层,让闪电更有“雷暴”氛围。
*
* @param {HTMLCanvasElement} canvas
* @param {CanvasRenderingContext2D} ctx
* @param {HTMLCanvasElement} canvas
* @param {CanvasRenderingContext2D} ctx
*/
function _drawStormGlow(canvas, ctx) {
const w = canvas.width;
const h = canvas.height;
const sky = ctx.createLinearGradient(0, 0, 0, h * 0.8);
sky.addColorStop(0, "rgba(7, 18, 38, 0.34)");
sky.addColorStop(0.45, "rgba(15, 23, 42, 0.18)");
sky.addColorStop(1, "rgba(15, 23, 42, 0)");
ctx.fillStyle = sky;
ctx.fillRect(0, 0, w, h);
for (let i = 0; i < 3; i++) {
const cloudX = w * (0.12 + Math.random() * 0.76);
const cloudY = h * (0.05 + Math.random() * 0.22);
const cloudR = 120 + Math.random() * 160;
const cloud = ctx.createRadialGradient(cloudX, cloudY, 0, cloudX, cloudY, cloudR);
cloud.addColorStop(0, "rgba(210, 226, 255, 0.18)");
cloud.addColorStop(0.38, "rgba(168, 196, 255, 0.1)");
cloud.addColorStop(1, "rgba(168, 196, 255, 0)");
ctx.fillStyle = cloud;
ctx.beginPath();
ctx.arc(cloudX, cloudY, cloudR, 0, Math.PI * 2);
ctx.fill();
}
}
/**
* 渲染一次闪电 + 闪屏效果。
*
* @param {HTMLCanvasElement} canvas
* @param {CanvasRenderingContext2D} ctx
*/
function _flash(canvas, ctx) {
const w = canvas.width;
@@ -63,24 +94,44 @@ const LightningEffect = (() => {
// 清空画布
ctx.clearRect(0, 0, w, h);
// 闪屏:全屏短暂泛白
ctx.fillStyle = "rgba(220, 235, 255, 0.55)";
// 先铺一层压暗天空,再叠加闪白,让明暗反差更明显。
_drawStormGlow(canvas, ctx);
ctx.fillStyle = "rgba(228, 239, 255, 0.46)";
ctx.fillRect(0, 0, w, h);
// 绘制 1-3 条主闪电(从随机顶部位置向下延伸)
const boltCount = Math.floor(Math.random() * 2) + 1;
// 绘制 1-3 条主闪电,并给出明显的白色核心线。
const boltCount = Math.floor(Math.random() * 3) + 1;
for (let i = 0; i < boltCount; i++) {
const x1 = w * (0.2 + Math.random() * 0.6);
const x1 = w * (0.12 + Math.random() * 0.76);
const y1 = 0;
const x2 = x1 + (Math.random() - 0.5) * 300;
const y2 = h * (0.5 + Math.random() * 0.4);
_drawBolt(ctx, x1, y1, x2, y2, 4, 3);
const x2 = x1 + (Math.random() - 0.5) * 360;
const y2 = h * (0.55 + Math.random() * 0.35);
const width = 3.6 + Math.random() * 1.8;
_drawBolt(ctx, x1, y1, x2, y2, 5, width);
ctx.save();
ctx.strokeStyle = "rgba(255,255,255,0.92)";
ctx.lineWidth = Math.max(1.2, width * 0.34);
ctx.shadowColor = "rgba(255,255,255,0.95)";
ctx.shadowBlur = 22;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
ctx.restore();
}
// 50ms 后让画布逐渐消退(模拟闪电短促感)
// 短促残影:闪电消失后保留一层很淡的余辉,避免“闪一下就没了”。
setTimeout(() => {
ctx.clearRect(0, 0, w, h);
}, 80);
_drawStormGlow(canvas, ctx);
ctx.fillStyle = "rgba(185, 205, 255, 0.12)";
ctx.fillRect(0, 0, w, h);
}, 90);
setTimeout(() => {
ctx.clearRect(0, 0, w, h);
}, 190);
}
/**
@@ -91,24 +142,40 @@ const LightningEffect = (() => {
*/
function start(canvas, onEnd) {
const ctx = canvas.getContext("2d");
const FLASHES = 10; // 总闪电次数(增加密度)
const DURATION = 7000; // 总时长(ms,相应延长)
const FLASHES = 9;
const DURATION = 7600;
let count = 0;
let finished = false;
/**
* 统一结束特效,避免多次触发 onEnd。
*/
function finish() {
if (finished) {
return;
}
finished = true;
ctx.clearRect(0, 0, canvas.width, canvas.height);
onEnd();
}
// 间隔不规则触发多次闪电(模拟真实雷电节奏)
function nextFlash() {
if (count >= FLASHES) {
// 全部闪完,结束特效
setTimeout(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
onEnd();
}, 400);
finish();
}, 520);
return;
}
_flash(canvas, ctx);
count++;
// 下次闪电间隔:400ms ~ 800ms 之间随机(更频繁)
const delay = 400 + Math.random() * 400;
// 让雷电节奏有“成组爆发”的感觉:有时连续两下,有时间隔更久。
const delay = Math.random() > 0.65
? 140 + Math.random() * 140
: 420 + Math.random() * 520;
setTimeout(nextFlash, delay);
}
@@ -117,8 +184,7 @@ const LightningEffect = (() => {
// 安全兜底:超时强制结束
setTimeout(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
onEnd();
finish();
}, DURATION + 500);
}