支持点击结束全屏特效
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
* 文件功能:聊天室特效管理器
|
* 文件功能:聊天室特效管理器
|
||||||
*
|
*
|
||||||
* 统一管理全屏 Canvas 特效的入口、防重入和资源清理。
|
* 统一管理全屏 Canvas 特效的入口、防重入和资源清理。
|
||||||
|
* 播放期间用户点击屏幕任意位置可立即结束当前全屏特效。
|
||||||
* 使用方式:EffectManager.play('fireworks' | 'rain' | 'lightning' | 'snow' | 'sakura' | 'meteors' | 'gold-rain' | 'hearts' | 'confetti' | 'fireflies')
|
* 使用方式:EffectManager.play('fireworks' | 'rain' | 'lightning' | 'snow' | 'sakura' | 'meteors' | 'gold-rain' | 'hearts' | 'confetti' | 'fireflies')
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -12,10 +13,16 @@ const EffectManager = (() => {
|
|||||||
let _canvas = null;
|
let _canvas = null;
|
||||||
// 待播放特效队列,避免多个进场效果互相打断
|
// 待播放特效队列,避免多个进场效果互相打断
|
||||||
const _queue = [];
|
const _queue = [];
|
||||||
|
// 当前特效播放批次,用于忽略手动停止后的旧回调
|
||||||
|
let _playToken = 0;
|
||||||
|
// 是否已经绑定本轮点击停止监听
|
||||||
|
let _clickStopBound = false;
|
||||||
|
// 延迟绑定定时器,避免触发播放的同一次点击立即停止特效
|
||||||
|
let _clickStopTimer = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取或创建全屏 Canvas 元素
|
* 获取或创建全屏 Canvas 元素
|
||||||
* 属性:fixed 定位,覆盖全屏,pointer-events:none 不阻止用户交互
|
* 属性:fixed 定位,覆盖全屏,播放期间接收点击用于立即停止特效。
|
||||||
*/
|
*/
|
||||||
function _getCanvas() {
|
function _getCanvas() {
|
||||||
if (_canvas && document.body.contains(_canvas)) {
|
if (_canvas && document.body.contains(_canvas)) {
|
||||||
@@ -30,7 +37,9 @@ const EffectManager = (() => {
|
|||||||
"width:100vw",
|
"width:100vw",
|
||||||
"height:100vh",
|
"height:100vh",
|
||||||
"z-index:99999",
|
"z-index:99999",
|
||||||
"pointer-events:none",
|
"pointer-events:auto",
|
||||||
|
"cursor:pointer",
|
||||||
|
"touch-action:manipulation",
|
||||||
].join(";");
|
].join(";");
|
||||||
c.width = window.innerWidth;
|
c.width = window.innerWidth;
|
||||||
c.height = window.innerHeight;
|
c.height = window.innerHeight;
|
||||||
@@ -40,9 +49,94 @@ const EffectManager = (() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 特效结束后清理 Canvas,重置状态,并停止音效
|
* 绑定点击屏幕立即停止当前特效的监听。
|
||||||
|
*
|
||||||
|
* 延后一帧绑定,避免触发特效的同一次点击被误判为结束点击。
|
||||||
*/
|
*/
|
||||||
function _cleanup() {
|
function _bindClickStop() {
|
||||||
|
if (_clickStopBound) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_clickStopTimer = window.setTimeout(() => {
|
||||||
|
if (!_current || _clickStopBound) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_clickStopBound = true;
|
||||||
|
if (_canvas) {
|
||||||
|
_canvas.addEventListener("pointerdown", _handleStopClick, {
|
||||||
|
capture: true,
|
||||||
|
});
|
||||||
|
_canvas.addEventListener("mousedown", _handleStopClick, {
|
||||||
|
capture: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
document.addEventListener("pointerdown", _handleStopClick, {
|
||||||
|
capture: true,
|
||||||
|
});
|
||||||
|
document.addEventListener("mousedown", _handleStopClick, {
|
||||||
|
capture: true,
|
||||||
|
});
|
||||||
|
}, 120);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解绑点击停止监听,避免特效结束后影响正常聊天操作。
|
||||||
|
*/
|
||||||
|
function _unbindClickStop() {
|
||||||
|
if (_clickStopTimer) {
|
||||||
|
window.clearTimeout(_clickStopTimer);
|
||||||
|
_clickStopTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_clickStopBound) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_canvas) {
|
||||||
|
_canvas.removeEventListener("pointerdown", _handleStopClick, {
|
||||||
|
capture: true,
|
||||||
|
});
|
||||||
|
_canvas.removeEventListener("mousedown", _handleStopClick, {
|
||||||
|
capture: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
document.removeEventListener("pointerdown", _handleStopClick, {
|
||||||
|
capture: true,
|
||||||
|
});
|
||||||
|
document.removeEventListener("mousedown", _handleStopClick, {
|
||||||
|
capture: true,
|
||||||
|
});
|
||||||
|
_clickStopBound = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理屏幕点击结束特效。
|
||||||
|
*/
|
||||||
|
function _handleStopClick(event) {
|
||||||
|
if (event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 特效结束后清理 Canvas,重置状态,并停止音效。
|
||||||
|
*
|
||||||
|
* @param {Object} options 清理选项
|
||||||
|
* @param {boolean} options.playNext 是否继续播放队列中的下一个特效
|
||||||
|
* @param {number|null} options.token 当前特效播放批次
|
||||||
|
*/
|
||||||
|
function _cleanup({ playNext = true, token = null } = {}) {
|
||||||
|
if (token !== null && token !== _playToken) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_playToken++;
|
||||||
|
_unbindClickStop();
|
||||||
|
|
||||||
if (_canvas && document.body.contains(_canvas)) {
|
if (_canvas && document.body.contains(_canvas)) {
|
||||||
document.body.removeChild(_canvas);
|
document.body.removeChild(_canvas);
|
||||||
}
|
}
|
||||||
@@ -53,7 +147,7 @@ const EffectManager = (() => {
|
|||||||
EffectSounds.stop();
|
EffectSounds.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_queue.length > 0) {
|
if (playNext && _queue.length > 0) {
|
||||||
const nextType = _queue.shift();
|
const nextType = _queue.shift();
|
||||||
if (nextType) {
|
if (nextType) {
|
||||||
play(nextType);
|
play(nextType);
|
||||||
@@ -76,6 +170,9 @@ const EffectManager = (() => {
|
|||||||
|
|
||||||
const canvas = _getCanvas();
|
const canvas = _getCanvas();
|
||||||
_current = type;
|
_current = type;
|
||||||
|
const token = _playToken;
|
||||||
|
const finishCurrent = () => _cleanup({ token });
|
||||||
|
_bindClickStop();
|
||||||
|
|
||||||
// 同步触发对应音效
|
// 同步触发对应音效
|
||||||
if (typeof EffectSounds !== "undefined") {
|
if (typeof EffectSounds !== "undefined") {
|
||||||
@@ -85,65 +182,79 @@ const EffectManager = (() => {
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case "fireworks":
|
case "fireworks":
|
||||||
if (typeof FireworksEffect !== "undefined") {
|
if (typeof FireworksEffect !== "undefined") {
|
||||||
FireworksEffect.start(canvas, _cleanup);
|
FireworksEffect.start(canvas, finishCurrent);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "wedding-fireworks":
|
case "wedding-fireworks":
|
||||||
// 婚礼专属:双倍礼花,粉金浪漫配色,持续 12 秒
|
// 婚礼专属:双倍礼花,粉金浪漫配色,持续 12 秒
|
||||||
if (typeof FireworksEffect !== "undefined") {
|
if (typeof FireworksEffect !== "undefined") {
|
||||||
FireworksEffect.startDouble(canvas, _cleanup);
|
FireworksEffect.startDouble(canvas, finishCurrent);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "rain":
|
case "rain":
|
||||||
if (typeof RainEffect !== "undefined") {
|
if (typeof RainEffect !== "undefined") {
|
||||||
RainEffect.start(canvas, _cleanup);
|
RainEffect.start(canvas, finishCurrent);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "lightning":
|
case "lightning":
|
||||||
if (typeof LightningEffect !== "undefined") {
|
if (typeof LightningEffect !== "undefined") {
|
||||||
LightningEffect.start(canvas, _cleanup);
|
LightningEffect.start(canvas, finishCurrent);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "snow":
|
case "snow":
|
||||||
if (typeof SnowEffect !== "undefined") {
|
if (typeof SnowEffect !== "undefined") {
|
||||||
SnowEffect.start(canvas, _cleanup);
|
SnowEffect.start(canvas, finishCurrent);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "sakura":
|
case "sakura":
|
||||||
if (typeof SakuraEffect !== "undefined") {
|
if (typeof SakuraEffect !== "undefined") {
|
||||||
SakuraEffect.start(canvas, _cleanup);
|
SakuraEffect.start(canvas, finishCurrent);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "meteors":
|
case "meteors":
|
||||||
if (typeof MeteorsEffect !== "undefined") {
|
if (typeof MeteorsEffect !== "undefined") {
|
||||||
MeteorsEffect.start(canvas, _cleanup);
|
MeteorsEffect.start(canvas, finishCurrent);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "gold-rain":
|
case "gold-rain":
|
||||||
if (typeof GoldRainEffect !== "undefined") {
|
if (typeof GoldRainEffect !== "undefined") {
|
||||||
GoldRainEffect.start(canvas, _cleanup);
|
GoldRainEffect.start(canvas, finishCurrent);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "hearts":
|
case "hearts":
|
||||||
if (typeof HeartsEffect !== "undefined") {
|
if (typeof HeartsEffect !== "undefined") {
|
||||||
HeartsEffect.start(canvas, _cleanup);
|
HeartsEffect.start(canvas, finishCurrent);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "confetti":
|
case "confetti":
|
||||||
if (typeof ConfettiEffect !== "undefined") {
|
if (typeof ConfettiEffect !== "undefined") {
|
||||||
ConfettiEffect.start(canvas, _cleanup);
|
ConfettiEffect.start(canvas, finishCurrent);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "fireflies":
|
case "fireflies":
|
||||||
if (typeof FirefliesEffect !== "undefined") {
|
if (typeof FirefliesEffect !== "undefined") {
|
||||||
FirefliesEffect.start(canvas, _cleanup);
|
FirefliesEffect.start(canvas, finishCurrent);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.warn(`[EffectManager] 未知特效类型:${type}`);
|
console.warn(`[EffectManager] 未知特效类型:${type}`);
|
||||||
_cleanup();
|
finishCurrent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { play };
|
/**
|
||||||
|
* 用户手动停止当前全屏特效。
|
||||||
|
*
|
||||||
|
* 会立即移除画布、停止音效,并清空排队中的后续特效。
|
||||||
|
*/
|
||||||
|
function stop() {
|
||||||
|
if (!_current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_queue.length = 0;
|
||||||
|
_cleanup({ playNext: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
return { play, stop };
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user