Greasy Fork

来自缓存

Greasy Fork is available in English.

万能懒加载触发 + 超级视窗缩放(面板可拖拽)

修复三种懒加载模式 + 可拖拽面板 + 优化按钮样式

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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

})();