Greasy Fork

Greasy Fork is available in English.

Ultimate Web Optimizer

全面的网页性能优化方案(含懒加载/预加载/预连接)

当前为 2025-06-09 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Ultimate Web Optimizer
// @namespace    http://greasyfork.icu/zh-CN/users/1474228-moyu001
// @version      1.2.1
// @description  全面的网页性能优化方案(含懒加载/预加载/预连接)
// @author       moyu001
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_log
// @license      MIT
// @run-at       document-start
// ==/UserScript==

// 防debugger拦截(需尽早注入)
(function() {
    // 拦截 Function 构造器
    const origFunc = Function.prototype.constructor;
    Function.prototype.constructor = function(...args) {
        const code = args.join(',');
        if (code.includes('debugger')) {
            return origFunc.call(this, code.replace(/debugger;?/g, ''));
        }
        return origFunc.apply(this, args);
    };
    // 拦截 eval
    const origEval = window.eval;
    window.eval = function(code) {
        if (typeof code === 'string' && code.includes('debugger')) {
            code = code.replace(/debugger;?/g, '');
        }
        return origEval(code);
    };
})();

(function() {
    'use strict';

    // ========================
    // 配置中心
    // ========================
    // 如需持久化配置,可结合GM_setValue/GM_getValue实现用户自定义
    const config = {
        debug: false,  // 开启调试日志
        features: {
            lazyLoad: {  // 图片懒加载
                enabled: true,
                minSize: 100,     // 最小处理尺寸(px)
                rootMargin: '200px',  // 提前加载距离
                skipHidden: true  // 跳过隐藏图片
            },
            preconnect: {  // 智能预连接
                enabled: true,
                whitelist: [  // 中国大陆优化白名单
                    'fonts.gstatic.com',
                    'cdnjs.cloudflare.com',
                    'unpkg.com',
                    'ajax.googleapis.com',
                    'maxcdn.bootstrapcdn.com',
                    'code.jquery.com',
                    'kit.fontawesome.com',
                    'fonts.googleapis.cn',
                    'fonts.loli.net',
                    'cdn.jsdelivr.net',
                    'cdn.bootcdn.net',
                    'cdn.bootcss.com',
                    'libs.baidu.com',
                    'cdn.staticfile.org',
                    'lf3-cdn-tos.bytecdntp.com',
                    'unpkg.zhimg.com',
                    'npm.elemecdn.com',
                    'g.alicdn.com'
                ],
                maxConnections: 5  // 最大预连接数
            },
            preload: {  // 资源预加载
                enabled: true,
                types: ['css', 'js', 'woff2'],  // 预加载类型
                maxPreloads: 5  // 最大预加载数
            },
            layout: {  // 布局稳定性
                stableImages: true,
                stableIframes: true
            }
        }
    };

    // 全局黑名单(可扩展)
    const optimizerSiteBlacklist = [
        // 'example.com',
        // 可添加其它不希望优化的站点
    ];
    // 各模块独立黑名单(可扩展)
    const lazyLoadSiteBlacklist = [
        // 'example.com',
    ];
    const preconnectSiteBlacklist = [
        // 'example.com',
    ];
    const preloadSiteBlacklist = [
        // 'example.com',
    ];
    const layoutSiteBlacklist = [
        // 'example.com',
    ];

    // ========================
    // 核心优化模块
    // ========================

    // 防抖工具
    function debounce(fn, delay) {
        let timer = null;
        return function(...args) {
            clearTimeout(timer);
            timer = setTimeout(() => fn.apply(this, args), delay);
        };
    }

    // 简单LRU缓存实现
    class LRUSet {
        constructor(limit) {
            this.limit = limit;
            this.map = new Map();
        }
        has(key) {
            return this.map.has(key);
        }
        add(key) {
            if (this.map.has(key)) {
                this.map.delete(key);
            }
            this.map.set(key, true);
            if (this.map.size > this.limit) {
                // 删除最早的
                const firstKey = this.map.keys().next().value;
                this.map.delete(firstKey);
            }
        }
        get size() {
            return this.map.size;
        }
    }

    // 定期清理工具
    function scheduleCleanup(fn, interval = 10 * 60 * 1000) { // 默认10分钟
        setInterval(fn, interval);
    }

    // 图片/视频懒加载系统
    const initLazyLoad = () => {
        if (optimizerSiteBlacklist.some(domain => location.hostname.includes(domain)) ||
            lazyLoadSiteBlacklist.some(domain => location.hostname.includes(domain))) {
            if (config.debug) console.log('[懒加载] 当前站点已在全局或懒加载黑名单,跳过懒加载优化');
            return;
        }
        if (!config.features.lazyLoad.enabled) return;
        try {
            let lazyCount = 0;
            const isLazyCandidate = (el) => {
                // 跳过已实现懒加载的图片(如有data-src/data-srcset属性)
                if (el.hasAttribute && (el.hasAttribute('data-src') || el.hasAttribute('data-srcset'))) return false;
                if (el.loading === 'eager') return false;
                if (el.complete) return false;
                if (el.src && el.src.startsWith('data:')) return false;
                if (config.features.lazyLoad.skipHidden &&
                    window.getComputedStyle(el).display === 'none') return false;
                const rect = el.getBoundingClientRect();
                return rect.width > config.features.lazyLoad.minSize &&
                       rect.height > config.features.lazyLoad.minSize;
            };

            const processMedia = (el) => {
                if ('loading' in HTMLImageElement.prototype && el.tagName === 'IMG') {
                    el.loading = 'lazy';
                }
                if (!el.dataset.src && el.src) {
                    el.dataset.src = el.src;
                    el.removeAttribute('src');
                }
                if (el.srcset && !el.dataset.srcset) {
                    el.dataset.srcset = el.srcset;
                    el.removeAttribute('srcset');
                }
                lazyCount++;
                return el;
            };

            // 批量处理工具(自适应大页面)
            function batchProcess(arr, fn, batchSize, cb) {
                // batchSize自适应:图片极多时分批更小
                if (!batchSize) {
                    if (arr.length > 1000) batchSize = 8;
                    else if (arr.length > 500) batchSize = 16;
                    else if (arr.length > 200) batchSize = 32;
                    else batchSize = 64;
                }
                let i = 0;
                function nextBatch() {
                    for (let j = 0; j < batchSize && i < arr.length; j++, i++) {
                        fn(arr[i]);
                    }
                    if (i < arr.length) {
                        (window.requestIdleCallback || window.requestAnimationFrame)(nextBatch);
                    } else if (cb) {
                        cb();
                    }
                }
                nextBatch();
            }

            if ('IntersectionObserver' in window) {
                const observer = new IntersectionObserver((entries) => {
                    entries.forEach(entry => {
                        if (entry.isIntersecting) {
                            const el = entry.target;
                            if (el.dataset.src) el.src = el.dataset.src;
                            if (el.dataset.srcset) el.srcset = el.dataset.srcset;
                            observer.unobserve(el);
                        }
                    });
                }, {
                    rootMargin: config.features.lazyLoad.rootMargin,
                    threshold: 0.01
                });

                // 批量observe初始图片和视频
                const imgs = Array.from(document.querySelectorAll('img')).filter(isLazyCandidate);
                const videos = Array.from(document.querySelectorAll('video')).filter(isLazyCandidate);
                batchProcess(imgs.concat(videos), el => observer.observe(processMedia(el)), undefined, () => {
                    if (config.debug) {
                        console.log(`[懒加载] 初始图片: ${imgs.length}, 视频: ${videos.length}`);
                    }
                });

                // 动态加载监听,合并多次变更
                let pendingMedia = [];
                let scheduled = false;
                function scheduleBatchObserve() {
                    if (scheduled) return;
                    scheduled = true;
                    (window.requestIdleCallback || window.requestAnimationFrame)(() => {
                        scheduled = false;
                        const arr = pendingMedia;
                        pendingMedia = [];
                        batchProcess(arr, el => observer.observe(processMedia(el)), undefined, () => {
                            if (config.debug && arr.length) {
                                console.log(`[懒加载] 新增图片/视频: ${arr.length}`);
                            }
                        });
                    });
                }
                const observeNewMedia = debounce(mutations => {
                    mutations.forEach(mutation => {
                        mutation.addedNodes.forEach(node => {
                            if ((node.nodeName === 'IMG' || node.nodeName === 'VIDEO') && isLazyCandidate(node)) {
                                pendingMedia.push(node);
                            }
                        });
                    });
                    if (pendingMedia.length) scheduleBatchObserve();
                }, 50);
                const mo = new MutationObserver(observeNewMedia);
                mo.observe(document.body, {
                    childList: true,
                    subtree: true
                });

                // 资源释放:监听移除节点,自动unobserve
                const unobserveRemoved = mutations => {
                    mutations.forEach(mutation => {
                        mutation.removedNodes.forEach(node => {
                            if ((node.nodeName === 'IMG' || node.nodeName === 'VIDEO')) {
                                observer.unobserve(node);
                            }
                        });
                    });
                };
                const mo2 = new MutationObserver(unobserveRemoved);
                mo2.observe(document.body, {
                    childList: true,
                    subtree: true
                });
            } else {
                // 兼容模式实现
                const checkVisible = throttle(() => {
                    document.querySelectorAll('img').forEach(img => {
                        if (isLazyCandidate(img) && !img.src) {
                            const rect = img.getBoundingClientRect();
                            if (rect.top < window.innerHeight +
                                parseInt(config.features.lazyLoad.rootMargin)) {
                                if (img.dataset.src) img.src = img.dataset.src;
                                if (img.dataset.srcset) img.srcset = img.dataset.srcset;
                            }
                        }
                    });
                }, 200);
                window.addEventListener('scroll', checkVisible);
                window.addEventListener('resize', checkVisible); // 新增resize监听
                checkVisible();
            }
        } catch (e) {
            if (config.debug) console.warn('[LazyLoad] 异常:', e);
        }
    };

    // 智能预连接系统
    const initSmartPreconnect = () => {
        if (optimizerSiteBlacklist.some(domain => location.hostname.includes(domain)) ||
            preconnectSiteBlacklist.some(domain => location.hostname.includes(domain))) {
            if (config.debug) console.log('[预连接] 当前站点已在全局或预连接黑名单,跳过预连接优化');
            return;
        }
        if (!config.features.preconnect.enabled) return;
        try {
            const processed = new LRUSet(config.features.preconnect.maxConnections);
            const whitelist = config.features.preconnect.whitelist;
            scheduleCleanup(() => {
                processed.map.clear && processed.map.clear();
                if (config.debug) console.log('[Preconnect] processed已清理');
            }, 10 * 60 * 1000);

            let preconnectCount = 0;
            const doPreconnect = (hostname) => {
                if (processed.has(hostname)) return;
                const link = document.createElement('link');
                link.rel = 'preconnect';
                link.href = `https://${hostname}`;
                document.head.appendChild(link);
                processed.add(hostname);
                preconnectCount++;
                if (config.debug) {
                    console.log('[Preconnect] 已连接:', hostname);
                }
            };

            const scanResources = () => {
                const resources = [
                    ...document.querySelectorAll('script[src], link[href], img[src]')
                ];
                resources.forEach(el => {
                    try {
                        const url = new URL(el.src || el.href);
                        const matched = whitelist.find(domain =>
                            url.hostname.endsWith(domain)
                        );
                        if (matched) {
                            doPreconnect(url.hostname);
                        }
                    } catch {}
                });
            };

            // MutationObserver回调防抖
            const debouncedScan = debounce(scanResources, 50);
            scanResources();
            setTimeout(scanResources, 2000);
            new MutationObserver(debouncedScan).observe(document.body, {
                childList: true,
                subtree: true
            });

            // 统计日志
            if (config.debug) {
                setTimeout(() => {
                    console.log(`[预连接] 本页已预连接: ${preconnectCount}`);
                }, 2500);
            }
        } catch (e) {
            if (config.debug) console.warn('[Preconnect] 异常:', e);
        }
    };

    // 资源预加载系统(修复字体预加载警告)
    const initResourcePreload = () => {
        if (optimizerSiteBlacklist.some(domain => location.hostname.includes(domain)) ||
            preloadSiteBlacklist.some(domain => location.hostname.includes(domain))) {
            if (config.debug) console.log('[预加载] 当前站点已在全局或预加载黑名单,跳过预加载优化');
            return;
        }
        if (!config.features.preload.enabled) return;
        try {
            const processed = new Set();
            const types = config.features.preload.types;
            const max = config.features.preload.maxPreloads;
            const cssCache = new Map();
            let preloadCount = 0;

            const shouldPreload = (url) => {
                const ext = url.split('.').pop().toLowerCase();
                return types.includes(ext);
            };

            const doPreload = (url, asType) => {
                if (processed.size >= max) return;
                if (processed.has(url)) return;
                const link = document.createElement('link');
                link.rel = 'preload';
                link.as = asType;
                link.href = url;
                if (asType === 'font') {
                    link.setAttribute('crossorigin', 'anonymous');
                    if (url.includes('.woff2')) {
                        link.type = 'font/woff2';
                    } else if (url.includes('.woff')) {
                        link.type = 'font/woff';
                    } else if (url.includes('.ttf')) {
                        link.type = 'font/ttf';
                    } else if (url.includes('.otf')) {
                        link.type = 'font/otf';
                    }
                } else if (asType === 'script') {
                    link.type = 'text/javascript';
                } else if (asType === 'style') {
                    link.type = 'text/css';
                }
                document.head.appendChild(link);
                processed.add(url);
                preloadCount++;
                if (config.debug) {
                    console.log('[Preload] 已预加载:', url);
                }
            };

            const processFont = (cssUrl, fontUrl) => {
                try {
                    const absoluteUrl = new URL(fontUrl, cssUrl).href;
                    if (fontUrl.startsWith('data:')) return;
                    if (shouldPreload(absoluteUrl)) {
                        doPreload(absoluteUrl, 'font');
                    }
                } catch (e) {
                    if (config.debug) {
                        console.warn('[Preload] 字体解析失败:', fontUrl, e);
                    }
                }
            };

            const scanResources = () => {
                document.querySelectorAll('link[rel="stylesheet"]').forEach(link => {
                    const cssUrl = link.href;
                    if (cssUrl && shouldPreload(cssUrl)) {
                        doPreload(cssUrl, 'style');
                        // fetch缓存
                        if (cssCache.has(cssUrl)) {
                            const text = cssCache.get(cssUrl);
                            const fontUrls = text.match(/url\(["']?([^")']+\.(woff2?|ttf|otf))["']?\)/gi) || [];
                            fontUrls.forEach(fullUrl => {
                                const cleanUrl = fullUrl.replace(/url\(["']?|["']?\)/g, '');
                                processFont(cssUrl, cleanUrl);
                            });
                        } else {
                            fetch(cssUrl)
                                .then(res => res.text())
                                .then(text => {
                                    cssCache.set(cssUrl, text);
                                    const fontUrls = text.match(/url\(["']?([^")']+\.(woff2?|ttf|otf))["']?\)/gi) || [];
                                    fontUrls.forEach(fullUrl => {
                                        const cleanUrl = fullUrl.replace(/url\(["']?|["']?\)/g, '');
                                        processFont(cssUrl, cleanUrl);
                                    });
                                })
                                .catch(e => {
                                    if (config.debug) {
                                        console.warn('[Preload] CSS获取失败:', cssUrl, e);
                                    }
                                });
                        }
                    }
                });
                document.querySelectorAll('script[src]').forEach(script => {
                    const src = script.src;
                    if (src && shouldPreload(src)) {
                        doPreload(src, 'script');
                    }
                });
            };

            // MutationObserver回调防抖
            const debouncedScan = debounce(scanResources, 50);
            scanResources();
            new MutationObserver(debouncedScan).observe(document.body, {
                childList: true,
                subtree: true
            });

            // 定期清理cssCache和processed,防止无限增长
            scheduleCleanup(() => {
                cssCache.clear();
                processed.clear();
                if (config.debug) console.log('[Preload] 缓存已清理');
            }, 10 * 60 * 1000); // 10分钟

            // 统计日志
            if (config.debug) {
                setTimeout(() => {
                    console.log(`[预加载] 本页已预加载: ${preloadCount}`);
                }, 2500);
            }
        } catch (e) {
            if (config.debug) console.warn('[Preload] 异常:', e);
        }
    };

    // 布局稳定性优化
    const initLayoutStabilization = () => {
        if (optimizerSiteBlacklist.some(domain => location.hostname.includes(domain)) ||
            layoutSiteBlacklist.some(domain => location.hostname.includes(domain))) {
            if (config.debug) console.log('[布局优化] 当前站点已在全局或布局黑名单,跳过布局优化');
            return;
        }
        try {
            const styles = [];
            if (config.features.layout.stableImages) {
                styles.push(`
                    img:not([width]):not([height]) {
                        min-height: 1px;
                        // display: block;
                        // max-width: 100%;
                    }
                    @supports (aspect-ratio: 1/1) {
                        img:not([width]):not([height]) {
                            aspect-ratio: attr(width) / attr(height);
                        }
                    }
                `);
            }
            if (config.features.layout.stableIframes) {
                styles.push(`
                    iframe:not([width]):not([height]) {
                        // width: 100%;
                        height: auto;
                        // aspect-ratio: 16/9;
                        // display: block;
                    }
                `);
            }
            if (styles.length) {
                const style = document.createElement('style');
                style.textContent = styles.join('\n');
                document.head.appendChild(style);
                if (config.debug) {
                    console.log('[布局优化] 样式已注入');
                }
            }
        } catch (e) {
            if (config.debug) console.warn('[Layout] 异常:', e);
        }
    };

    // ========================
    // 工具函数
    // ========================
    function throttle(func, limit) {
        let inThrottle;
        return function() {
            if (!inThrottle) {
                func.apply(this, arguments);
                inThrottle = true;
                setTimeout(() => inThrottle = false, limit);
            }
        };
    }

    // ========================
    // 初始化系统
    // ========================
    document.addEventListener('DOMContentLoaded', () => {
        initSmartPreconnect();
        initResourcePreload();
        initLazyLoad();
        initLayoutStabilization();

        if (config.debug) {
            console.groupCollapsed('[Optimizer] 初始化报告');
            console.log('激活功能:', Object.entries(config.features)
                .filter(([_, v]) => v.enabled !== false)
                .map(([k]) => k));
            console.log('预连接白名单:', config.features.preconnect.whitelist);
            console.log('预加载类型:', config.features.preload.types);
            console.groupEnd();
        }
    });
})();