Files
chatroom/public/js/effects/lightning.js

193 lines
6.3 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.
/**
* 文件功能:聊天室雷电特效
*
* 使用递归分叉算法叠加云层闪光、主闪电、余辉残影,
* 在聊天室中模拟更有压迫感的雷暴闪电效果。
*/
const LightningEffect = (() => {
/**
* 递归绘制闪电路径(分裂算法)
*
* @param {CanvasRenderingContext2D} ctx
* @param {number} x1 起点 x
* @param {number} y1 起点 y
* @param {number} x2 终点 x
* @param {number} y2 终点 y
* @param {number} depth 当前递归深度(控制分叉层数)
* @param {number} width 线条宽度
*/
function _drawBolt(ctx, x1, y1, x2, y2, depth, width) {
if (depth <= 0) return;
// 中点随机偏移(越深层偏移越小,产生流畅感)
const mx = (x1 + x2) / 2 + (Math.random() - 0.5) * 80 * depth;
const my = (y1 + y2) / 2 + (Math.random() - 0.5) * 20 * depth;
const glow = ctx.createLinearGradient(x1, y1, x2, y2);
glow.addColorStop(0, "rgba(200, 220, 255, 0.9)");
glow.addColorStop(1, "rgba(150, 180, 255, 0.6)");
ctx.save();
ctx.strokeStyle = glow;
ctx.lineWidth = width;
ctx.shadowColor = "#aaccff";
ctx.shadowBlur = 18;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.quadraticCurveTo(mx, my, x2, y2);
ctx.stroke();
ctx.restore();
// 递归绘制两段子路径
_drawBolt(ctx, x1, y1, mx, my, depth - 1, width * 0.65);
_drawBolt(ctx, mx, my, x2, y2, depth - 1, width * 0.65);
// 随机在中途分叉一条小支路50% 概率)
if (depth > 1 && Math.random() > 0.5) {
const bx = mx + (Math.random() - 0.5) * 120;
const by = my + Math.random() * 80 + 40;
_drawBolt(ctx, mx, my, bx, by, depth - 2, width * 0.4);
}
}
/**
* 绘制顶部乌云压光层,让闪电更有“雷暴”氛围。
*
* @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;
const h = canvas.height;
// 清空画布
ctx.clearRect(0, 0, w, h);
// 先铺一层压暗天空,再叠加闪白,让明暗反差更明显。
_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() * 3) + 1;
for (let i = 0; i < boltCount; i++) {
const x1 = w * (0.12 + Math.random() * 0.76);
const y1 = 0;
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();
}
// 短促残影:闪电消失后保留一层很淡的余辉,避免“闪一下就没了”。
setTimeout(() => {
ctx.clearRect(0, 0, w, h);
_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);
}
/**
* 启动雷电特效
*
* @param {HTMLCanvasElement} canvas 全屏 Canvas
* @param {Function} onEnd 特效结束回调
*/
function start(canvas, onEnd) {
const ctx = canvas.getContext("2d");
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(() => {
finish();
}, 520);
return;
}
_flash(canvas, ctx);
count++;
// 让雷电节奏有“成组爆发”的感觉:有时连续两下,有时间隔更久。
const delay = Math.random() > 0.65
? 140 + Math.random() * 140
: 420 + Math.random() * 520;
setTimeout(nextFlash, delay);
}
// 短暂延迟后开始第一次闪电
setTimeout(nextFlash, 300);
// 安全兜底:超时强制结束
setTimeout(() => {
finish();
}, DURATION + 500);
}
return { start };
})();