收口聊天室安全边界并优化特效生命周期
This commit is contained in:
@@ -358,6 +358,22 @@ const FireworksEffect = (() => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在粒子预算内追加粒子,避免主爆炸阶段瞬间超量。
|
||||
*
|
||||
* @param {Particle[]} target
|
||||
* @param {Particle[]} incoming
|
||||
* @param {number} budget
|
||||
*/
|
||||
function _appendParticlesWithinBudget(target, incoming, budget) {
|
||||
const remaining = Math.max(0, budget - target.length);
|
||||
if (remaining <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
_appendParticles(target, incoming.slice(0, remaining));
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一发射一枚火箭。
|
||||
*
|
||||
@@ -388,9 +404,14 @@ const FireworksEffect = (() => {
|
||||
const ctx = canvas.getContext("2d");
|
||||
const w = canvas.width;
|
||||
const h = canvas.height;
|
||||
const isMobile = window.matchMedia?.("(max-width: 640px)")?.matches || window.innerWidth <= 640;
|
||||
const mobileScale = isMobile ? 0.72 : 1;
|
||||
const duration = config.duration;
|
||||
const hardStopAt = duration + 2600;
|
||||
const peakParticleBudget = config.peakParticleBudget ?? 1650;
|
||||
const peakParticleBudget = Math.round((config.peakParticleBudget ?? 1650) * mobileScale);
|
||||
const maxLaunches = Math.max(8, Math.round(config.maxLaunches * mobileScale));
|
||||
const particleDensity = config.particleDensity * mobileScale;
|
||||
const secondaryDensity = config.secondaryDensity * mobileScale;
|
||||
|
||||
let rockets = [];
|
||||
let particles = [];
|
||||
@@ -398,15 +419,17 @@ const FireworksEffect = (() => {
|
||||
let scheduledBursts = [];
|
||||
let animId = null;
|
||||
let launchCnt = 0;
|
||||
let finished = false;
|
||||
const timers = [];
|
||||
|
||||
const launchInterval = setInterval(() => {
|
||||
if (launchCnt >= config.maxLaunches) {
|
||||
if (launchCnt >= maxLaunches) {
|
||||
clearInterval(launchInterval);
|
||||
return;
|
||||
}
|
||||
|
||||
const batchSize = config.getBatchSize(launchCnt);
|
||||
for (let i = 0; i < batchSize && launchCnt < config.maxLaunches; i++) {
|
||||
for (let i = 0; i < batchSize && launchCnt < maxLaunches; i++) {
|
||||
_launchRocket(
|
||||
rockets,
|
||||
w,
|
||||
@@ -421,9 +444,9 @@ const FireworksEffect = (() => {
|
||||
|
||||
// 开场礼炮先把气氛撑起来,避免一开始太空。
|
||||
if (typeof config.openingVolley === "function") {
|
||||
setTimeout(() => {
|
||||
timers.push(setTimeout(() => {
|
||||
config.openingVolley(rockets, w, h);
|
||||
}, 120);
|
||||
}, 120));
|
||||
}
|
||||
|
||||
const startTime = performance.now();
|
||||
@@ -440,9 +463,10 @@ const FireworksEffect = (() => {
|
||||
for (let i = scheduledBursts.length - 1; i >= 0; i--) {
|
||||
if (scheduledBursts[i].triggerAt <= now) {
|
||||
const burst = scheduledBursts[i];
|
||||
_appendParticles(
|
||||
_appendParticlesWithinBudget(
|
||||
particles,
|
||||
_burst(burst.x, burst.y, burst.color, burst.type, burst.density),
|
||||
peakParticleBudget,
|
||||
);
|
||||
halos.push(new Halo(burst.x, burst.y, burst.color, burst.haloRadius));
|
||||
scheduledBursts.splice(i, 1);
|
||||
@@ -452,15 +476,16 @@ const FireworksEffect = (() => {
|
||||
for (let i = rockets.length - 1; i >= 0; i--) {
|
||||
const rocket = rockets[i];
|
||||
if (rocket.done) {
|
||||
_appendParticles(
|
||||
_appendParticlesWithinBudget(
|
||||
particles,
|
||||
_burst(
|
||||
rocket.x,
|
||||
rocket.y,
|
||||
rocket.color,
|
||||
rocket.type,
|
||||
config.particleDensity,
|
||||
particleDensity,
|
||||
),
|
||||
peakParticleBudget,
|
||||
);
|
||||
halos.push(new Halo(rocket.x, rocket.y, rocket.color, config.primaryHaloRadius));
|
||||
|
||||
@@ -475,7 +500,7 @@ const FireworksEffect = (() => {
|
||||
y: rocket.y + (Math.random() - 0.5) * 26,
|
||||
color: _pick(config.colors),
|
||||
type: Math.random() > 0.5 ? "sphere" : "ring",
|
||||
density: config.secondaryDensity,
|
||||
density: secondaryDensity,
|
||||
haloRadius: config.secondaryHaloRadius,
|
||||
});
|
||||
}
|
||||
@@ -502,14 +527,44 @@ const FireworksEffect = (() => {
|
||||
if (shouldContinue && elapsed < hardStopAt) {
|
||||
animId = requestAnimationFrame(animate);
|
||||
} else {
|
||||
clearInterval(launchInterval);
|
||||
cancelAnimationFrame(animId);
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
onEnd();
|
||||
finish(false);
|
||||
}
|
||||
}
|
||||
|
||||
animId = requestAnimationFrame(animate);
|
||||
|
||||
/**
|
||||
* 统一结束烟花演出,取消时不再回调管理器。
|
||||
*
|
||||
* @param {boolean} canceled 是否为手动取消
|
||||
*/
|
||||
function finish(canceled) {
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
finished = true;
|
||||
clearInterval(launchInterval);
|
||||
timers.forEach((timer) => clearTimeout(timer));
|
||||
if (animId) {
|
||||
cancelAnimationFrame(animId);
|
||||
}
|
||||
rockets = [];
|
||||
particles = [];
|
||||
halos = [];
|
||||
scheduledBursts = [];
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
|
||||
if (!canceled) {
|
||||
onEnd();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
cancel() {
|
||||
finish(true);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -519,7 +574,7 @@ const FireworksEffect = (() => {
|
||||
* @param {Function} onEnd 特效结束回调
|
||||
*/
|
||||
function start(canvas, onEnd) {
|
||||
_runShow(canvas, onEnd, {
|
||||
return _runShow(canvas, onEnd, {
|
||||
duration: 10500,
|
||||
launchEvery: 340,
|
||||
maxLaunches: 24,
|
||||
@@ -577,7 +632,7 @@ const FireworksEffect = (() => {
|
||||
"#00ddff", // 其他
|
||||
];
|
||||
|
||||
_runShow(canvas, onEnd, {
|
||||
return _runShow(canvas, onEnd, {
|
||||
duration: 12400,
|
||||
launchEvery: 280,
|
||||
maxLaunches: 34,
|
||||
|
||||
Reference in New Issue
Block a user