// ==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();
})();
Wrap
Beautify