跳转到内容

MediaWiki:Common.js:修订间差异

第125行: 第125行:
});
});
//---
//---
// 显示右下角 loading 图标
// IIFE (Immediately Invoked Function Expression)
$(function () {
(function() {
     // 如果已加载,不再添加 loading
     // --- 1. 尽早执行和检查重复 ---
     if (window.__loadingIndicatorInit) return;
    // 如果已初始化,不再执行任何操作
     window.__loadingIndicatorInit = true;
     if (window.__loadingIndicatorActive) { // 使用一个更明确的标志,表示 loading 当前是否“活跃”
     // 设置 loading 图像
        return;
     var loadingImgUrl = 'https://wiki.ottohub.cn/images/0/02/Loading.png'; // 替换路径为你上传后的图片地址
    }
    // ✅ 插入元素(强制 256x256 大小)
     window.__loadingIndicatorActive = true; // 标记 loading 开始
     var $loadingDiv = $('<div>', {
 
        id: 'loadingIndicator',
     // --- 2. 尽早创建和插入 Loading DOM ---
        css: {
     var loadingImgUrl = 'https://wiki.ottohub.cn/images/0/02/Loading.png';
            position: 'fixed',
     var loadingDiv = document.createElement('div');
            bottom: '20px',
    loadingDiv.id = 'loadingIndicator';
            right: '20px',
    loadingDiv.style.position = 'fixed';
            width: '256px',
    loadingDiv.style.bottom = '20px';
            height: '256px',
    loadingDiv.style.right = '20px';
            'z-index': 9999,
    loadingDiv.style.width = '256px'; // 强制大小,如果图片本身就是这个尺寸,可以省略
             'pointer-events': 'none'
    loadingDiv.style.height = '256px';
    loadingDiv.style.zIndex = '99999'; // 更高的 z-index 确保在最上层
    loadingDiv.style.pointerEvents = 'none'; // 允许点击穿透
    // loadingDiv.style.display = 'none'; // 先隐藏,通过 fadeIn 显示,可选
 
    var img = document.createElement('img');
    img.src = loadingImgUrl;
    img.alt = 'Loading...';
    img.style.width = '100%';
    img.style.height = '100%';
    img.style.objectFit = 'contain';
 
    loadingDiv.appendChild(img);
 
    // 确保 body 存在。如果此脚本在 <head> 中且没有 defer/async,
    // document.body 可能为 null。最安全的方式是把脚本放在 </body> 前。
    // 或者,如果必须在 head 中,则等待 DOMContentLoaded 附加。
    // 但为了“尽早显示”,我们这里会尝试立即附加,并提供回退。
 
    function appendLoadingIcon() {
        if (document.body) {
            document.body.appendChild(loadingDiv);
            // 可选:使用 fadeIn 效果,如果上面设置了 display: 'none'
            // loadingDiv.style.display = 'block'; // 或者使用 jQuery fadeIn
            // $(loadingDiv).stop(true, true).fadeIn(100); // 如果你还想用 jQuery 做动画
        } else {
            // 如果 body 还不存在,等待 DOMContentLoaded
            // 这意味着 loading 图标的显示会稍有延迟
             document.addEventListener('DOMContentLoaded', function onReady() {
                if (document.body && !document.getElementById('loadingIndicator')) { // 再次检查,防止重复
                    document.body.appendChild(loadingDiv);
                    // $(loadingDiv).stop(true, true).fadeIn(100);
                }
                document.removeEventListener('DOMContentLoaded', onReady); // 清理监听器
            });
         }
         }
     }).append(
     }
        $('<img>', {
 
            src: loadingImgUrl,
     appendLoadingIcon(); // 尝试立即附加
            alt: 'Loading...',
 
            css: {
                width: '100%',
                height: '100%',
                'object-fit': 'contain'
            }
        })
     );


     $('body').append($loadingDiv);
     // --- 3. 移除逻辑 ---
    var removalTimeoutId = null; // 用于清除兜底 timeout


    // ✅ 封装移除函数
     function removeLoading(reason) {
     function removeLoading(reason) {
    console.log(`Attempting to remove loading. Reason: ${reason}. Timestamp: ${Date.now()}`); // 新增
        // 确保只移除一次,并清除兜底 timeout
    const $el = $('#loadingIndicator');
        if (!window.__loadingIndicatorActive) return; // 如果已经移除了,则不操作
    if ($el.length) {
 
        console.log(`[Common.js] 🔄 Removing loading. Element found. Reason: ${reason}`);
        var el = document.getElementById('loadingIndicator');
        $el.stop(true, true).fadeOut(300, function () {
        if (el) {
            console.log(`[Common.js] ✅ Loading removed. Reason: ${reason}`); // 新增
            console.log(`[Common.js] 🔄 Removing loading. Reason: ${reason}`);
             $(this).remove();
            // 使用原生 JS 实现 fadeOut 效果 (可选,或直接 remove)
         });
            el.style.transition = 'opacity 0.3s ease-out';
    } else {
            el.style.opacity = '0';
        console.log(`[Common.js] ⚠️ Loading element #loadingIndicator not found. Reason: ${reason}`); // 新增
            setTimeout(function() {
                if (el.parentNode) {
                    el.parentNode.removeChild(el);
                }
                console.log(`[Common.js] ✅ Loading removed. Reason: ${reason}`);
             }, 300); // 等待 fadeOut 动画完成
         } else {
            // console.log(`[Common.js] ⚠️ Loading indicator already removed or not found when trying to remove. Reason: ${reason}`);
        }
        window.__loadingIndicatorActive = false; // 标记 loading 已移除
        if (removalTimeoutId) {
            clearTimeout(removalTimeoutId); // 清除兜底的10秒timeout
            removalTimeoutId = null;
        }
        // 清理事件监听器,防止内存泄漏(对于只触发一次的事件,现代浏览器通常会自动处理,但显式移除更安全)
        window.removeEventListener('load', onWindowLoad);
        document.removeEventListener('DOMContentLoaded', onDOMContentLoaded);
     }
     }
}


     // ✅ 确保能正常卸载 loading:
     // --- 4. 检查即时状态并设置事件监听 ---


     // 情况1:标准资源加载完成
     // 立即检查 document.readyState (处理缓存或极快加载)
     window.addEventListener('load', function () {
     if (document.readyState === 'complete') {
    console.log('window.load event fired');
        // 使用 requestAnimationFrame 或短 setTimeout 给浏览器一点点时间渲染 loading 图标
    removeLoading('window.load');
        // 然后再移除,避免完全看不到。
});
        // 如果不希望看到闪烁,可以直接调用 removeLoading。
        requestAnimationFrame(function() { // 或者 setTimeout(..., 0)
            removeLoading('document.readyState was already complete on script exec');
        });
        return; // 已经加载完成,不需要后续的监听器和 timeout
    }


     // 情况2:页面 DOM 已构建(但不一定所有资源加载完成)
     // 定义事件处理函数,以便之后可以移除它们
     document.addEventListener('DOMContentLoaded', function () {
     function onWindowLoad() {
    console.log('DOMContentLoaded event fired. document.readyState:', document.readyState);
        removeLoading('window.load');
        // 如果页面状态已经是 complete,则提前移除(补漏)
    }
         if (document.readyState === 'complete') {
    function onDOMContentLoaded() {
            removeLoading('document.readyState === complete in DOMContentLoaded');
         if (document.readyState === 'complete' || document.readyState === 'interactive') {
        } else {
            // DOMContentLoaded 之后,如果已经是 complete 或 interactive,也可以认为主要内容加载完成
             // 等待微任务完成
             // 使用 setTimeout 给一个微小的延迟,确保其他 DOMContentLoaded 任务有机会执行
             setTimeout(function () {
             setTimeout(function() {
                 if (document.readyState === 'interactive' || document.readyState === 'complete') {
                 removeLoading('DOMContentLoaded (interactive/complete)');
                    removeLoading('DOMContentLoaded fallback');
             }, 0); // 极短延迟
                }
             }, 100); // 延迟一点
         }
         }
     });
        // 如果不是 complete/interactive,则 window.load 仍然是主要依赖
     }


     // 情况3:兜底策略,10 秒后强制关闭
     // 注册事件监听
     setTimeout(function () {
    window.addEventListener('load', onWindowLoad);
    document.addEventListener('DOMContentLoaded', onDOMContentLoaded);
 
    // 兜底策略:10 秒后强制关闭
     removalTimeoutId = setTimeout(function() {
         removeLoading('timeout fallback (10s)');
         removeLoading('timeout fallback (10s)');
     }, 10000);
     }, 10000);
});
 
})();