127 lines
4.2 KiB
JavaScript
127 lines
4.2 KiB
JavaScript
/**
|
||
* 文件功能:聊天室雷电特效
|
||
*
|
||
* 使用递归分叉算法在 Canvas 上绘制真实感闪电路径,
|
||
* 配合全屏闪白效果模拟雷闪。总持续约 5 秒,结束后回调。
|
||
*/
|
||
|
||
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 _flash(canvas, ctx) {
|
||
const w = canvas.width;
|
||
const h = canvas.height;
|
||
|
||
// 清空画布
|
||
ctx.clearRect(0, 0, w, h);
|
||
|
||
// 闪屏:全屏短暂泛白
|
||
ctx.fillStyle = "rgba(220, 235, 255, 0.55)";
|
||
ctx.fillRect(0, 0, w, h);
|
||
|
||
// 绘制 1-3 条主闪电(从随机顶部位置向下延伸)
|
||
const boltCount = Math.floor(Math.random() * 2) + 1;
|
||
for (let i = 0; i < boltCount; i++) {
|
||
const x1 = w * (0.2 + Math.random() * 0.6);
|
||
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);
|
||
}
|
||
|
||
// 50ms 后让画布逐渐消退(模拟闪电短促感)
|
||
setTimeout(() => {
|
||
ctx.clearRect(0, 0, w, h);
|
||
}, 80);
|
||
}
|
||
|
||
/**
|
||
* 启动雷电特效
|
||
*
|
||
* @param {HTMLCanvasElement} canvas 全屏 Canvas
|
||
* @param {Function} onEnd 特效结束回调
|
||
*/
|
||
function start(canvas, onEnd) {
|
||
const ctx = canvas.getContext("2d");
|
||
const FLASHES = 5; // 总闪电次数
|
||
const DURATION = 5000; // 总时长(ms)
|
||
let count = 0;
|
||
|
||
// 间隔不规则触发多次闪电(模拟真实雷电节奏)
|
||
function nextFlash() {
|
||
if (count >= FLASHES) {
|
||
// 全部闪完,结束特效
|
||
setTimeout(() => {
|
||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||
onEnd();
|
||
}, 500);
|
||
return;
|
||
}
|
||
_flash(canvas, ctx);
|
||
count++;
|
||
// 下次闪电间隔:800ms ~ 1200ms 之间随机
|
||
const delay = 700 + Math.random() * 500;
|
||
setTimeout(nextFlash, delay);
|
||
}
|
||
|
||
// 短暂延迟后开始第一次闪电
|
||
setTimeout(nextFlash, 300);
|
||
|
||
// 安全兜底:超时强制结束
|
||
setTimeout(() => {
|
||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||
onEnd();
|
||
}, DURATION + 500);
|
||
}
|
||
|
||
return { start };
|
||
})();
|