源码安装
1090197965 / 【云】收藏夹图片懒加载

// ==UserScript==
// @name         【云】收藏夹图片懒加载
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  串行加载带有lazy-src属性的图片,避免资源抢占,支持优先级加载
// @author       You
// @match        https://fubiji.site/*
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';
    
    // 配置参数,从GM存储中获取或使用默认值
    const config = {
        concurrentLoads: GM_getValue('concurrentLoads', 1), // 同时加载图片数量
        scanInterval: GM_getValue('scanInterval', 1000),    // 扫描间隔(毫秒)
        loadDelay: GM_getValue('loadDelay', 100),           // 加载间隔(毫秒)
        enabled: GM_getValue('enabled', true)               // 是否启用脚本
    };
    
    // 存储需要加载的图片元素
    let imagesToLoad = [];
    // 当前正在加载的图片数量
    let currentlyLoading = 0;
    // 定时扫描的计时器ID
    let scanTimer = null;
    // 上次加载图片的时间戳
    let lastLoadTime = 0;
    // 是否有待处理的加载请求
    let pendingLoadRequest = false;
    
    // 扫描页面查找带有lazy-src属性的图片
    function scanLazyImages() {
        if (!config.enabled) return;
        
        const lazyImages = document.querySelectorAll('img[lazy-src]');
        
        // 过滤出尚未加入加载队列且尚未加载的图片
        const newImages = Array.from(lazyImages).filter(img => {
            // 检查图片是否已经在队列中
            const inQueue = imagesToLoad.includes(img);
            // 检查图片是否已经加载过(通过自定义属性标记)
            const isLoaded = img.getAttribute('data-loaded') === 'true';
            
            return !inQueue && !isLoaded;
        });
        
        // 将新发现的图片添加到加载队列
        if (newImages.length > 0) {
            console.log(`发现${newImages.length}张新的懒加载图片`);
            imagesToLoad = imagesToLoad.concat(newImages);
            
            // 根据lazy-level属性对图片进行排序
            sortImagesByPriority();
            
            // 尝试加载图片
            tryLoadImages();
        }
    }
    
    // 根据优先级对图片队列进行排序
    function sortImagesByPriority() {
        imagesToLoad.sort((a, b) => {
            // 获取优先级,默认为最大值999(最低优先级)
            const levelA = parseInt(a.getAttribute('lazy-level') || '999', 10);
            const levelB = parseInt(b.getAttribute('lazy-level') || '999', 10);
            
            // 优先级数字越小,优先级越高
            return levelA - levelB;
        });
        
        // 输出排序后的优先级信息
        if (imagesToLoad.length > 0) {
            console.log('图片已按优先级排序,队列前5项优先级:', 
                imagesToLoad.slice(0, 5).map(img => img.getAttribute('lazy-level') || '999').join(', '));
        }
    }
    
    // 尝试加载图片,确保同时加载的图片数不超过设置值,且遵循加载间隔
    function tryLoadImages() {
        if (!config.enabled) return;
        
        // 如果没有图片需要加载,则不处理
        if (imagesToLoad.length === 0) return;
        
        // 如果已达到并发加载上限,则不处理
        if (currentlyLoading >= config.concurrentLoads) return;
        
        const now = Date.now();
        const timeSinceLastLoad = now - lastLoadTime;
        
        // 检查是否满足加载间隔要求
        if (timeSinceLastLoad < config.loadDelay) {
            // 如果间隔不够,且没有待处理的请求,设置定时器
            if (!pendingLoadRequest) {
                pendingLoadRequest = true;
                setTimeout(() => {
                    pendingLoadRequest = false;
                    tryLoadImages();
                }, config.loadDelay - timeSinceLastLoad);
            }
            return;
        }
        
        // 更新最后加载时间
        lastLoadTime = now;
        
        // 加载下一张图片
        loadImage(imagesToLoad.shift());
        
        // 如果还有图片需要加载且未达到并发上限,继续尝试加载
        if (imagesToLoad.length > 0 && currentlyLoading < config.concurrentLoads) {
            tryLoadImages();
        }
    }
    
    // 加载单张图片
    function loadImage(img) {
        if (!img) return;
        
        const lazySrc = img.getAttribute('lazy-src');
        if (!lazySrc) return;
        
        const lazyLevel = img.getAttribute('lazy-level') || '999';
        
        currentlyLoading++;
        console.log('正在加载图片:', lazySrc, `(优先级: ${lazyLevel}, 当前同时加载: ${currentlyLoading}/${config.concurrentLoads})`);
        
        // 创建一个新的Image对象来预加载图片
        const tempImg = new Image();
        
        tempImg.onload = function() {
            // 图片加载成功后,设置到原始img元素
            img.src = lazySrc;
            // 标记为已加载
            img.setAttribute('data-loaded', 'true');
            // 移除lazy-src属性,防止重复加载
            img.removeAttribute('lazy-src');
            
            console.log('图片加载完成:', lazySrc, `(优先级: ${lazyLevel})`);
            
            currentlyLoading--;
            
            // 延迟一小段时间再尝试加载更多图片
            setTimeout(() => {
                tryLoadImages();
            }, config.loadDelay);
        };
        
        tempImg.onerror = function() {
            // 加载失败时,标记为已处理,继续下一张
            console.error('图片加载失败:', lazySrc, `(优先级: ${lazyLevel})`);
            img.setAttribute('data-loaded', 'true');
            
            currentlyLoading--;
            
            // 尝试加载更多图片
            setTimeout(() => {
                tryLoadImages();
            }, config.loadDelay);
        };
        
        // 开始加载图片
        tempImg.src = lazySrc;
    }
    
    // 更新启用状态按钮的显示
    function updateEnableButtonStatus() {
        const enableBtn = document.getElementById('lazyload-toggle');
        if (!enableBtn) return;
        
        if (config.enabled) {
            enableBtn.textContent = '已启用 - 点击禁用';
            enableBtn.style.backgroundColor = '#2ecc71';
        } else {
            enableBtn.textContent = '已禁用 - 点击启用';
            enableBtn.style.backgroundColor = '#e74c3c';
        }
    }
    
    // 切换启用状态
    function toggleEnabled() {
        config.enabled = !config.enabled;
        GM_setValue('enabled', config.enabled);
        
        // 更新按钮状态
        updateEnableButtonStatus();
        
        // 重新设置扫描定时器
        if (scanTimer) {
            clearInterval(scanTimer);
        }
        
        if (config.enabled) {
            scanTimer = setInterval(scanLazyImages, config.scanInterval);
            // 立即扫描一次
            scanLazyImages();
            
            // 显示状态
            const statusElem = document.getElementById('lazyload-status');
            if (statusElem) {
                statusElem.textContent = '懒加载已启用!';
                statusElem.style.color = '#2ecc71';
                
                setTimeout(() => {
                    statusElem.textContent = '';
                }, 2000);
            }
        } else {
            // 显示状态
            const statusElem = document.getElementById('lazyload-status');
            if (statusElem) {
                statusElem.textContent = '懒加载已禁用!';
                statusElem.style.color = '#e74c3c';
                
                setTimeout(() => {
                    statusElem.textContent = '';
                }, 2000);
            }
            
            // 当禁用懒加载时,直接加载所有带有lazy-src属性的图片
            loadAllImagesDirectly();
        }
    }
    
    // 当禁用懒加载时,直接加载所有图片
    function loadAllImagesDirectly() {
        const lazyImages = document.querySelectorAll('img[lazy-src]');
        console.log(`禁用懒加载,直接加载所有${lazyImages.length}张图片`);
        
        lazyImages.forEach(img => {
            const lazySrc = img.getAttribute('lazy-src');
            if (lazySrc) {
                // 直接设置src属性
                img.src = lazySrc;
                img.setAttribute('data-loaded', 'true');
                img.removeAttribute('lazy-src');
            }
        });
    }
    
    // 创建设置面板
    function createSettingsPanel() {
        // 创建设置按钮
        const settingsBtn = document.createElement('div');
        settingsBtn.innerHTML = '⚙️';
        settingsBtn.title = '图片懒加载设置';
        settingsBtn.style.cssText = `
            position: fixed;
            left: 10px;
            bottom: 10px;
            width: 40px;
            height: 40px;
            background-color: #3498db;
            color: white;
            border-radius: 50%;
            text-align: center;
            line-height: 40px;
            font-size: 20px;
            cursor: pointer;
            z-index: 9999;
            box-shadow: 0 2px 5px rgba(0,0,0,0.3);
            transition: all 0.3s ease;
        `;
        
        // 鼠标悬停效果
        settingsBtn.addEventListener('mouseover', () => {
            settingsBtn.style.backgroundColor = '#2980b9';
        });
        
        settingsBtn.addEventListener('mouseout', () => {
            settingsBtn.style.backgroundColor = '#3498db';
        });
        
        // 创建设置面板
        const settingsPanel = document.createElement('div');
        settingsPanel.style.cssText = `
            position: fixed;
            left: 10px;
            bottom: 60px;
            width: 300px;
            background-color: white;
            border-radius: 5px;
            padding: 15px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
            z-index: 9998;
            display: none;
            font-family: Arial, sans-serif;
        `;
        
        // 设置面板内容
        settingsPanel.innerHTML = `
            <h3 style="margin-top: 0; color: #333;">图片懒加载设置</h3>
            <div style="margin-bottom: 15px;">
                <button id="lazyload-toggle" style="width: 100%; padding: 12px; font-size: 16px; border: none; border-radius: 4px; cursor: pointer; transition: background-color 0.3s;">
                    ${config.enabled ? '已启用 - 点击禁用' : '已禁用 - 点击启用'}
                </button>
            </div>
            <div style="margin-bottom: 15px;">
                <label style="display: block; margin-bottom: 5px; font-size: 16px;">同时加载图片数量:</label>
                <input type="number" id="lazyload-concurrent" value="${config.concurrentLoads}" min="1" max="10" style="width: 100%; padding: 10px; box-sizing: border-box; font-size: 16px;">
            </div>
            <div style="margin-bottom: 15px;">
                <label style="display: block; margin-bottom: 5px; font-size: 16px;">扫描间隔(毫秒):</label>
                <input type="number" id="lazyload-scan-interval" value="${config.scanInterval}" min="500" step="500" style="width: 100%; padding: 10px; box-sizing: border-box; font-size: 16px;">
            </div>
            <div style="margin-bottom: 15px;">
                <label style="display: block; margin-bottom: 5px; font-size: 16px;">加载延迟(毫秒):</label>
                <input type="number" id="lazyload-delay" value="${config.loadDelay}" min="0" step="50" style="width: 100%; padding: 10px; box-sizing: border-box; font-size: 16px;">
            </div>
            <div style="display: flex; justify-content: space-between;">
                <button id="lazyload-save" style="padding: 12px 20px; background-color: #2ecc71; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 16px;">保存</button>
                <button id="lazyload-cancel" style="padding: 12px 20px; background-color: #e74c3c; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 16px;">取消</button>
            </div>
            <div id="lazyload-status" style="margin-top: 10px; font-size: 14px; color: #666;"></div>
            <div style="margin-top: 10px; font-size: 14px; color: #666;">
                <p>使用说明: 在img标签添加lazy-src属性设置延迟加载的图片地址,添加lazy-level属性设置加载优先级(数字越小优先级越高)</p>
            </div>
        `;
        
        // 添加到页面
        document.body.appendChild(settingsBtn);
        document.body.appendChild(settingsPanel);
        
        // 设置启用/禁用按钮的颜色
        updateEnableButtonStatus();
        
        // 点击按钮显示/隐藏设置面板
        settingsBtn.addEventListener('click', () => {
            if (settingsPanel.style.display === 'none') {
                settingsPanel.style.display = 'block';
            } else {
                settingsPanel.style.display = 'none';
            }
        });
        
        // 启用/禁用按钮点击事件
        document.getElementById('lazyload-toggle').addEventListener('click', toggleEnabled);
        
        // 保存设置
        document.getElementById('lazyload-save').addEventListener('click', () => {
            // 获取设置值
            const concurrentLoads = parseInt(document.getElementById('lazyload-concurrent').value, 10) || 1;
            const scanInterval = parseInt(document.getElementById('lazyload-scan-interval').value, 10) || 1000;
            const loadDelay = parseInt(document.getElementById('lazyload-delay').value, 10) || 100;
            
            // 更新配置
            config.concurrentLoads = concurrentLoads;
            config.scanInterval = scanInterval;
            config.loadDelay = loadDelay;
            
            // 保存到GM存储
            GM_setValue('concurrentLoads', concurrentLoads);
            GM_setValue('scanInterval', scanInterval);
            GM_setValue('loadDelay', loadDelay);
            
            // 重新设置扫描定时器
            if (scanTimer) {
                clearInterval(scanTimer);
            }
            
            if (config.enabled) {
                scanTimer = setInterval(scanLazyImages, config.scanInterval);
                // 立即扫描一次
                scanLazyImages();
            }
            
            // 显示保存成功
            const statusElem = document.getElementById('lazyload-status');
            statusElem.textContent = '设置已保存!';
            statusElem.style.color = '#2ecc71';
            
            setTimeout(() => {
                statusElem.textContent = '';
            }, 2000);
        });
        
        // 取消按钮
        document.getElementById('lazyload-cancel').addEventListener('click', () => {
            settingsPanel.style.display = 'none';
        });
        
        // 点击其他区域关闭设置面板
        document.addEventListener('click', (e) => {
            if (!settingsPanel.contains(e.target) && e.target !== settingsBtn) {
                settingsPanel.style.display = 'none';
            }
        });
    }
    
    // 初始化:设置定时扫描
    function initialize() {
        console.log('【云】收藏夹图片懒加载脚本已启动');
        
        // 创建设置面板
        createSettingsPanel();
        
        // 如果启用了脚本,开始扫描
        if (config.enabled) {
            // 立即进行第一次扫描
            scanLazyImages();
            
            // 设置定时扫描
            scanTimer = setInterval(scanLazyImages, config.scanInterval);
        } else {
            // 如果脚本默认禁用,直接加载所有图片
            loadAllImagesDirectly();
        }
    }
    
    // 启动脚本
    initialize();
})();