Greasy Fork is available in English.
修复三种懒加载模式 + 可拖拽面板 + 优化按钮样式
// ==UserScript==
// @name 万能懒加载触发 + 超级视窗缩放(面板可拖拽)
// @namespace qq:1316590732
// @version 9.1
// @description 修复三种懒加载模式 + 可拖拽面板 + 优化按钮样式
// @author 沧桑
// @match *://*/*
// @grant none
// @license MIT
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
let currentZoom = 1;
let panel = null;
let isDragging = false;
let startX = 0, startY = 0, startLeft = 0, startTop = 0;
let scrollInterval = null; // 用于控制滚动频率
// ========== 核心缩放函数 ==========
function setZoom(zoomLevel) {
currentZoom = Math.max(0.0001, Math.min(50, zoomLevel));
if ('zoom' in document.body.style) {
document.body.style.zoom = currentZoom;
} else {
document.body.style.transform = `scale(${currentZoom})`;
document.body.style.transformOrigin = '0 0';
}
updateZoomDisplay();
}
function updateZoomDisplay() {
if (!panel) return;
const display = panel.querySelector('#currentZoomDisplay');
if (display) {
display.textContent = currentZoom < 0.01
? `${currentZoom.toExponential(4)} 倍`
: `${currentZoom.toFixed(4)} 倍`;
}
}
// ========== 模拟真实鼠标事件 ==========
function simulateMouseEvent(element, eventType) {
if (!element) return;
const rect = element.getBoundingClientRect();
const x = rect.left + rect.width / 2;
const y = rect.top + rect.height / 2;
const event = new MouseEvent(eventType, {
view: window,
bubbles: true,
cancelable: true,
clientX: x,
clientY: y,
screenX: x,
screenY: y
});
element.dispatchEvent(event);
}
// ========== 触发当前视口内所有图片的加载 ==========
function triggerImagesInViewport() {
const viewportHeight = window.innerHeight;
const viewportWidth = window.innerWidth;
const scrollX = window.scrollX || window.pageXOffset;
const scrollY = window.scrollY || window.pageYOffset;
// 获取所有可能的懒加载图片
const selectors = [
'img[data-src]', 'img[data-original]', 'img[data-lazy]', 'img[data-lazy-src]',
'img[data-srcset]', 'img[loading="lazy"]', 'img[data-echo]', 'img[data-url]',
'img[data-srcset]', 'img[data-ks-lazyload]', 'img[srcset]', 'img:not([src])',
'iframe[data-src]', 'iframe[data-lazy]'
];
const allImages = document.querySelectorAll(selectors.join(','));
let loadedCount = 0;
allImages.forEach(img => {
const rect = img.getBoundingClientRect();
// 检查是否在视口内或接近视口(增加200px缓冲)
const isInViewport = (
rect.top < viewportHeight + 200 &&
rect.bottom > -200 &&
rect.left < viewportWidth + 200 &&
rect.right > -200
);
if (isInViewport) {
// 模拟鼠标悬停
simulateMouseEvent(img, 'mouseenter');
simulateMouseEvent(img, 'mouseover');
// 强制加载图片
if (img.tagName === 'IMG') {
if (img.dataset.src && !img.src) img.src = img.dataset.src;
if (img.dataset.original && !img.src) img.src = img.dataset.original;
if (img.dataset.lazy && !img.src) img.src = img.dataset.lazy;
if (img.dataset.lazySrc && !img.src) img.src = img.dataset.lazySrc;
if (img.dataset.url && !img.src) img.src = img.dataset.url;
if (img.dataset.echo && !img.src) img.src = img.dataset.echo;
// 处理 srcset
if (img.dataset.srcset && !img.srcset) img.srcset = img.dataset.srcset;
loadedCount++;
}
else if (img.tagName === 'IFRAME') {
if (img.dataset.src && !img.src) img.src = img.dataset.src;
if (img.dataset.lazy && !img.src) img.src = img.dataset.lazy;
loadedCount++;
}
}
});
if (loadedCount > 0) {
console.log(`[懒加载] 触发加载 ${loadedCount} 个资源`);
}
// 触发全局事件
window.dispatchEvent(new Event('scroll'));
window.dispatchEvent(new Event('resize'));
document.dispatchEvent(new Event('scroll'));
document.dispatchEvent(new Event('visibilitychange'));
}
// ========== 滚动时持续触发加载 ==========
function startContinuousTrigger() {
if (scrollInterval) clearInterval(scrollInterval);
// 每200ms触发一次视口内图片检查
scrollInterval = setInterval(() => {
triggerImagesInViewport();
}, 200);
}
function stopContinuousTrigger() {
if (scrollInterval) {
clearInterval(scrollInterval);
scrollInterval = null;
}
}
// ========== 改进后的懒加载滚动 ==========
async function triggerLazyLoad(mode = 'vertical') {
console.log(`[懒加载] 开始触发 → 模式: ${mode}`);
// 启动持续触发
startContinuousTrigger();
// 先重置到左上角
window.scrollTo({ left: 0, top: 0, behavior: 'auto' });
// 触发一次初始加载
triggerImagesInViewport();
await sleep(300);
const totalHeight = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
const totalWidth = Math.max(document.documentElement.scrollWidth, document.body.scrollWidth);
const step = Math.max(220, window.innerHeight * 0.7); // 垂直步长
const hStep = Math.max(280, window.innerWidth * 0.75); // 水平步长
let pos = 0;
if (mode === 'horizontal-ltr') { // 从左往右
pos = 0;
while (pos <= totalWidth + 600) {
window.scrollTo({ left: pos, top: 0, behavior: 'auto' });
await sleep(250); // 增加停留时间,让懒加载有机会加载
triggerImagesInViewport(); // 滚动后立即触发
await sleep(150);
pos += hStep;
}
window.scrollTo({ left: 0, top: 0, behavior: 'smooth' });
}
else if (mode === 'horizontal-rtl') { // 从右往左
pos = totalWidth;
while (pos >= -600) {
window.scrollTo({ left: pos, top: 0, behavior: 'auto' });
await sleep(250);
triggerImagesInViewport();
await sleep(150);
pos -= hStep;
}
window.scrollTo({ left: totalWidth, top: 0, behavior: 'smooth' });
}
else { // 从上往下(默认)
pos = 0;
while (pos <= totalHeight + 600) {
window.scrollTo({ top: pos, left: 0, behavior: 'auto' });
await sleep(250);
triggerImagesInViewport();
await sleep(150);
pos += step;
}
window.scrollTo({ top: 0, behavior: 'smooth' });
}
// 最终再触发一次所有图片
await sleep(500);
triggerAllImagesManually();
// 停止持续触发
stopContinuousTrigger();
console.log('[懒加载] 完成');
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// ========== 加强版手动触发所有图片和iframe加载 ==========
function triggerAllImagesManually() {
const selectors = [
'img[data-src]', 'img[data-original]', 'img[data-lazy]', 'img[data-lazy-src]',
'img[data-srcset]', 'img[loading="lazy"]', 'img[data-echo]', 'img[data-url]',
'img:not([src])', 'img[src=""]', 'img[src="#"]',
'iframe[data-src]', 'iframe[data-lazy]'
];
let loadedCount = 0;
document.querySelectorAll(selectors.join(',')).forEach(el => {
if (el.tagName === 'IMG') {
// 模拟鼠标事件
simulateMouseEvent(el, 'mouseenter');
simulateMouseEvent(el, 'mouseover');
if (el.dataset.src && !el.src) {
el.src = el.dataset.src;
loadedCount++;
}
if (el.dataset.original && !el.src) {
el.src = el.dataset.original;
loadedCount++;
}
if (el.dataset.lazy && !el.src) {
el.src = el.dataset.lazy;
loadedCount++;
}
if (el.dataset.lazySrc && !el.src) {
el.src = el.dataset.lazySrc;
loadedCount++;
}
if (el.dataset.url && !el.src) {
el.src = el.dataset.url;
loadedCount++;
}
if (el.dataset.srcset && !el.srcset) {
el.srcset = el.dataset.srcset;
loadedCount++;
}
}
else if (el.tagName === 'IFRAME') {
if (el.dataset.src && !el.src) {
el.src = el.dataset.src;
loadedCount++;
}
if (el.dataset.lazy && !el.src) {
el.src = el.dataset.lazy;
loadedCount++;
}
}
});
if (loadedCount > 0) {
console.log(`[手动触发] 强制加载 ${loadedCount} 个资源`);
}
// 触发全局事件
window.dispatchEvent(new Event('scroll'));
document.dispatchEvent(new Event('scroll'));
document.body.dispatchEvent(new Event('mouseenter'));
}
// ========== 使用MutationObserver监听新添加的图片 ==========
function observeNewImages() {
const observer = new MutationObserver((mutations) => {
let hasNewImages = false;
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === 1) { // Element node
if (node.tagName === 'IMG' || node.tagName === 'IFRAME') {
hasNewImages = true;
}
if (node.querySelectorAll) {
const images = node.querySelectorAll('img, iframe');
if (images.length > 0) hasNewImages = true;
}
}
});
});
if (hasNewImages) {
setTimeout(() => triggerImagesInViewport(), 100);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
console.log('[监听] 已启用新图片监听');
}
// ========== 创建面板(保持之前样式) ==========
function createPanel() {
if (panel && document.documentElement.contains(panel)) return;
panel = document.createElement('div');
panel.id = 'super-zoom-panel';
Object.assign(panel.style, {
position: 'fixed',
top: '20px',
right: '20px',
width: '350px',
background: 'linear-gradient(135deg, #667eea, #764ba2)',
color: 'white',
borderRadius: '16px',
padding: '18px',
boxShadow: '0 12px 50px rgba(0,0,0,0.45)',
zIndex: '2147483647',
fontFamily: 'system-ui, sans-serif',
backdropFilter: 'blur(12px)',
border: '1px solid rgba(255,255,255,0.3)',
userSelect: 'none',
transform: 'none !important',
zoom: '1 !important'
});
panel.innerHTML = `
<div id="panelHeader" style="display:flex; justify-content:space-between; align-items:center; margin-bottom:12px; padding-bottom:10px; border-bottom:1px solid rgba(255,255,255,0.3); cursor:move;">
<span style="font-weight:600; font-size:15px;">🔍 沧桑超级视窗</span>
<button id="closeBtn" style="background:none;border:none;color:white;font-size:20px;cursor:pointer;">✕</button>
</div>
<div style="font-size:13px; opacity:0.85; margin-bottom:10px;">
<strong>按住标题栏</strong> 可拖动面板
<br/>
<strong>版本 9.1 - 需手动点击按钮触发加载</strong>
<br/>
<a href="https://space.bilibili.com/507560163" target="_blank"
style="color:white; font-weight:bold; text-decoration:none;">
点击跳转,欢迎到B站打赏、充电支持作者,UID:507560163
</a>
<br/>
<strong>#####</strong>
<br/>
</div>
<div style="margin:12px 0 16px; font-size:14px; font-weight:500;" id="currentZoomDisplay">1.0000 倍</div>
<div style="margin-bottom:18px;">
<div style="font-size:13px; margin-bottom:8px; opacity:0.9;">页面缩放比例</div>
<div style="display:grid; grid-template-columns: repeat(3, 1fr); gap:8px;">
<button data-zoom="0.0001" class="zoom-btn">0.0001×</button>
<button data-zoom="0.01" class="zoom-btn">0.01×</button>
<button data-zoom="0.05" class="zoom-btn">0.05×</button>
<button data-zoom="1" class="zoom-btn">1× 正常</button>
<button data-zoom="10" class="zoom-btn">10×</button>
</div>
</div>
<div>
<div style="font-size:13px; margin-bottom:8px; opacity:0.9;">懒加载触发模式</div>
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:8px;">
<button id="lazyVertical" class="lazy-btn">📜 从上往下</button>
<button id="lazyLTR" class="lazy-btn">↔️ 从左往右</button>
<button id="lazyRTL" class="lazy-btn">↔️ 从右往左</button>
</div>
</div>
<div style="margin-top:12px;">
<button id="forceLoadBtn" class="reset-btn">🖱️ 强制加载所有图片</button>
</div>
<div style="margin-top:16px;">
<button id="resetBtn" class="reset-btn">⟳ 重置缩放</button>
</div>
`;
document.documentElement.appendChild(panel);
// 按钮样式
const style = document.createElement('style');
style.textContent = `
#super-zoom-panel .zoom-btn,
#super-zoom-panel .lazy-btn,
#super-zoom-panel .reset-btn {
padding: 12px 8px; border: none; border-radius: 10px;
background: rgba(255,255,255,0.22); color: white;
font-size: 13.5px; font-weight: 600; cursor: pointer;
transition: all 0.2s;
}
#super-zoom-panel .zoom-btn:hover,
#super-zoom-panel .lazy-btn:hover { background: rgba(255,255,255,0.38); transform: translateY(-1px); }
#super-zoom-panel .zoom-btn:active,
#super-zoom-panel .lazy-btn:active { transform: scale(0.95); }
#super-zoom-panel .reset-btn {
width: 100%; padding: 13px; background: rgba(255,255,255,0.18);
}
#super-zoom-panel .reset-btn:hover { background: rgba(255,255,255,0.28); }
`;
panel.appendChild(style);
// 事件绑定
panel.querySelectorAll('button[data-zoom]').forEach(btn => {
btn.addEventListener('click', () => setZoom(parseFloat(btn.dataset.zoom)));
});
panel.querySelector('#lazyVertical').addEventListener('click', () => triggerLazyLoad('vertical'));
panel.querySelector('#lazyLTR').addEventListener('click', () => triggerLazyLoad('horizontal-ltr'));
panel.querySelector('#lazyRTL').addEventListener('click', () => triggerLazyLoad('horizontal-rtl'));
panel.querySelector('#forceLoadBtn').addEventListener('click', () => triggerAllImagesManually());
panel.querySelector('#resetBtn').addEventListener('click', () => setZoom(1));
panel.querySelector('#closeBtn').addEventListener('click', () => panel.remove());
makeDraggable(panel);
updateZoomDisplay();
}
// 拖拽功能(保持不变)
function makeDraggable(element) {
const header = element.querySelector('#panelHeader');
header.addEventListener('mousedown', (e) => {
if (e.target.id === 'closeBtn') return;
isDragging = true;
startX = e.clientX; startY = e.clientY;
const rect = element.getBoundingClientRect();
startLeft = rect.left; startTop = rect.top;
element.style.transition = 'none';
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
let newLeft = startLeft + (e.clientX - startX);
let newTop = startTop + (e.clientY - startY);
newLeft = Math.max(10, Math.min(window.innerWidth - element.offsetWidth - 10, newLeft));
newTop = Math.max(10, Math.min(window.innerHeight - element.offsetHeight - 10, newTop));
element.style.left = newLeft + 'px';
element.style.top = newTop + 'px';
element.style.right = 'auto';
element.style.bottom = 'auto';
});
document.addEventListener('mouseup', () => {
isDragging = false;
if (panel) panel.style.transition = 'all 0.2s';
});
}
function keepPanelAlive() {
if (!panel || !document.documentElement.contains(panel)) createPanel();
setTimeout(keepPanelAlive, 2000);
}
function init() {
createPanel();
keepPanelAlive();
observeNewImages(); // 监听新添加的图片(仅监听,不自动强制加载)
setTimeout(() => setZoom(1), 800);
// 【关键修改】移除了页面加载时自动触发图片加载的代码
// 现在需要用户点击面板按钮才会开始加载图片
// 注意:observeNewImages 会持续监听新添加的图片,
// 但需要用户先点击触发加载后,才会对后续新图片也生效。
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();