// ==UserScript== // @name Ultimate Web Optimizer // @namespace https://greasyfork.org/zh-CN/users/1474228-moyu001 // @version 2.0 // @description 全面的网页性能优化方案(可视化配置版)- 懒加载/预加载/预连接/布局优化 // @author moyu001 // @match *://*/* // @grant GM_getValue // @grant GM_setValue // @grant GM_log // @license MIT // @run-at document-start // @downloadURL none // ==/UserScript== (function() { 'use strict'; // ======================== // 工具函数 - 提前定义 // ======================== /** * 防抖函数 * @param {Function} fn 要防抖的函数 * @param {number} delay 延迟时间 * @returns {Function} 防抖后的函数 */ function debounce(fn, delay) { let timer = null; return function(...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); }; } /** * 节流函数 * @param {Function} func 要节流的函数 * @param {number} limit 限制时间 * @returns {Function} 节流后的函数 */ function throttle(func, limit) { let inThrottle; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } /** * 安全的 URL 解析 * @param {string} url URL 字符串 * @param {string} base 基准 URL * @returns {URL|null} URL 对象或 null */ function safeParseURL(url, base) { try { return new URL(url, base); } catch { return null; } } /** * 检查元素是否可见 * @param {Element} element 要检查的元素 * @returns {boolean} 是否可见 */ function isElementVisible(element) { if (!element) return false; const style = window.getComputedStyle(element); return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'; } /** * 深度合并对象 * @param {Object} target 目标对象 * @param {Object} source 源对象 * @returns {Object} 合并后的对象 */ function deepMerge(target, source) { const result = { ...target }; for (const key in source) { if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) { result[key] = deepMerge(result[key] || {}, source[key]); } else { result[key] = source[key]; } } return result; } /** * 检查是否为 HTML 图片元素 - 增强类型检查 * @param {Node} node DOM 节点 * @returns {boolean} 是否为图片元素 */ function isImageElement(node) { return node instanceof HTMLImageElement; } /** * 延迟函数 * @param {number} ms 延迟毫秒数 * @returns {Promise} Promise 对象 */ function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * 简单的 LRU 缓存实现 */ class LRUCache { constructor(maxSize = 100) { this.maxSize = maxSize; this.cache = new Map(); } get(key) { if (this.cache.has(key)) { const value = this.cache.get(key); this.cache.delete(key); this.cache.set(key, value); return value; } return null; } set(key, value) { if (this.cache.has(key)) { this.cache.delete(key); } else if (this.cache.size >= this.maxSize) { const firstKey = this.cache.keys().next().value; this.cache.delete(firstKey); } this.cache.set(key, value); } has(key) { return this.cache.has(key); } clear() { this.cache.clear(); } get size() { return this.cache.size; } } /** * 重试操作工具类 - 新增错误重试机制 */ class RetryableOperation { /** * 执行带重试的操作 * @param {Function} operation 要执行的操作 * @param {number} maxRetries 最大重试次数 * @param {number} baseDelay 基础延迟时间 * @returns {Promise} 操作结果 */ static async executeWithRetry(operation, maxRetries = 3, baseDelay = 1000) { for (let i = 0; i < maxRetries; i++) { try { return await operation(); } catch (e) { if (i === maxRetries - 1) throw e; await delay(baseDelay * (i + 1)); } } } } /** * 性能监控器 */ class PerformanceMonitor { constructor(debug = false) { this.debug = debug; this.metrics = new Map(); this.counters = new Map(); } start(name) { if (this.debug) { this.metrics.set(name, performance.now()); } } end(name) { if (this.debug && this.metrics.has(name)) { const duration = performance.now() - this.metrics.get(name); console.log(`[性能] ${name}: ${duration.toFixed(2)}ms`); this.metrics.delete(name); return duration; } return 0; } count(name) { if (this.debug) { this.counters.set(name, (this.counters.get(name) || 0) + 1); } } getCounter(name) { return this.counters.get(name) || 0; } profile(name, fn) { if (!this.debug) return fn(); const start = performance.now(); const result = fn(); const end = performance.now(); console.log(`[性能] ${name}: ${(end - start).toFixed(2)}ms`); return result; } log(message, ...args) { if (this.debug) { console.log(`[优化器] ${message}`, ...args); } } warn(message, ...args) { if (this.debug) { console.warn(`[优化器] ${message}`, ...args); } } error(message, ...args) { if (this.debug) { console.error(`[优化器] ${message}`, ...args); } } } // ======================== // 可视化配置界面 // ======================== /** * 可视化配置管理器 */ class VisualConfigManager { constructor(configManager) { this.configManager = configManager; this.isVisible = false; this.configPanel = null; this.dragOffset = { x: 0, y: 0 }; this.isDragging = false; this.createConfigPanel(); this.setupKeyboardShortcut(); } createConfigPanel() { // 创建样式 this.injectStyles(); // 创建主面板 this.configPanel = document.createElement('div'); this.configPanel.id = 'web-optimizer-config'; this.configPanel.className = 'wo-config-panel wo-hidden'; this.configPanel.innerHTML = this.generatePanelHTML(); document.body.appendChild(this.configPanel); // 绑定事件 this.bindEvents(); } injectStyles() { const style = document.createElement('style'); style.id = 'web-optimizer-styles'; style.textContent = ` .wo-config-panel { position: fixed; top: 50px; right: 20px; width: 400px; max-height: 80vh; background: #ffffff; border: 1px solid #e1e5e9; border-radius: 12px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); z-index: 2147483647; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 14px; line-height: 1.5; overflow: hidden; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); backdrop-filter: blur(10px); } .wo-config-panel.wo-hidden { opacity: 0; visibility: hidden; transform: translateX(100%) scale(0.95); } .wo-config-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 16px 20px; cursor: move; user-select: none; display: flex; justify-content: space-between; align-items: center; } .wo-config-title { font-weight: 600; font-size: 16px; margin: 0; } .wo-config-version { background: rgba(255, 255, 255, 0.2); padding: 2px 8px; border-radius: 12px; font-size: 11px; font-weight: 500; } .wo-config-close { background: none; border: none; color: white; font-size: 20px; cursor: pointer; padding: 4px; border-radius: 4px; transition: background-color 0.2s; } .wo-config-close:hover { background: rgba(255, 255, 255, 0.2); } .wo-config-content { max-height: calc(80vh - 80px); overflow-y: auto; padding: 0; } .wo-config-content::-webkit-scrollbar { width: 6px; } .wo-config-content::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 3px; } .wo-config-section { border-bottom: 1px solid #f0f0f0; } .wo-config-section:last-child { border-bottom: none; } .wo-section-header { background: #f8f9fa; padding: 12px 20px; font-weight: 600; color: #2c3e50; border-left: 4px solid #667eea; cursor: pointer; display: flex; justify-content: space-between; align-items: center; transition: background-color 0.2s; } .wo-section-header:hover { background: #e9ecef; } .wo-section-toggle { font-size: 12px; transition: transform 0.2s; } .wo-section-toggle.collapsed { transform: rotate(-90deg); } .wo-section-content { padding: 16px 20px; display: block; } .wo-section-content.collapsed { display: none; } .wo-config-item { margin-bottom: 16px; } .wo-config-item:last-child { margin-bottom: 0; } .wo-config-label { display: block; font-weight: 500; color: #2c3e50; margin-bottom: 6px; } .wo-config-description { font-size: 12px; color: #6c757d; margin-bottom: 8px; line-height: 1.4; } .wo-config-input { width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; transition: border-color 0.2s, box-shadow 0.2s; box-sizing: border-box; } .wo-config-input:focus { outline: none; border-color: #667eea; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } .wo-config-checkbox { display: flex; align-items: center; cursor: pointer; } .wo-config-checkbox input { margin-right: 8px; transform: scale(1.2); } .wo-config-textarea { width: 100%; min-height: 80px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 13px; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; resize: vertical; box-sizing: border-box; } .wo-config-actions { padding: 16px 20px; background: #f8f9fa; border-top: 1px solid #e1e5e9; display: flex; gap: 8px; } .wo-btn { padding: 8px 16px; border: none; border-radius: 6px; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.2s; flex: 1; } .wo-btn-primary { background: #667eea; color: white; } .wo-btn-primary:hover { background: #5a67d8; transform: translateY(-1px); } .wo-btn-secondary { background: #e2e8f0; color: #4a5568; } .wo-btn-secondary:hover { background: #cbd5e0; } .wo-btn-danger { background: #e53e3e; color: white; } .wo-btn-danger:hover { background: #c53030; } .wo-stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 12px; } .wo-stat-item { background: #f8f9fa; padding: 12px; border-radius: 6px; text-align: center; } .wo-stat-value { font-size: 18px; font-weight: 600; color: #667eea; display: block; } .wo-stat-label { font-size: 11px; color: #6c757d; margin-top: 2px; } .wo-toggle-btn { position: fixed; bottom: 20px; right: 20px; width: 56px; height: 56px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; border-radius: 50%; color: white; font-size: 20px; cursor: pointer; box-shadow: 0 4px 16px rgba(102, 126, 234, 0.4); z-index: 2147483646; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); display: flex; align-items: center; justify-content: center; } .wo-toggle-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5); } .wo-toggle-btn.wo-hidden { opacity: 0; visibility: hidden; transform: translateY(100px) scale(0.8); } @media (max-width: 480px) { .wo-config-panel { width: calc(100vw - 40px); right: 20px; left: 20px; max-height: 90vh; } } `; document.head.appendChild(style); } generatePanelHTML() { const config = this.configManager.config; return `

Web Optimizer 配置

v2.0
${this.generateGeneralSection(config)} ${this.generateLazyLoadSection(config)} ${this.generatePreconnectSection(config)} ${this.generatePreloadSection(config)} ${this.generateLayoutSection(config)} ${this.generateStatsSection()}
`; } generateGeneralSection(config) { return `
🔧 基础设置
开启后将在控制台显示详细的运行日志和性能信息
这些域名将完全跳过所有优化功能,每行一个域名
`; } generateLazyLoadSection(config) { const lazyLoad = config.lazyLoad; return `
🖼️ 懒加载设置
自动延迟加载页面图片,提升页面加载速度
小于此尺寸的图片将不进行懒加载处理
图片距离视窗多远时开始加载(如:200px)
每次处理的图片数量,较大值处理更快但可能影响性能
不处理当前不可见的图片元素
这些域名将跳过懒加载功能,每行一个域名
`; } generatePreconnectSection(config) { const preconnect = config.preconnect; return `
🔗 预连接设置
自动预连接到常用CDN和资源服务器,减少连接延迟
同时预连接的最大域名数量
符合这些域名的资源将被预连接,每行一个域名
`; } generatePreloadSection(config) { const preload = config.preload; return `
📦 预加载设置
预加载关键CSS、JS和字体资源,提升加载速度
同时预加载的最大资源数量
将预加载这些类型的文件,用逗号分隔
获取CSS文件以提取字体的超时时间
请求失败时的最大重试次数
`; } generateLayoutSection(config) { const layout = config.layout; return `
📐 布局优化设置
为无尺寸元素添加默认样式,减少布局偏移
为没有明确尺寸的图片设置默认样式
为iframe设置默认的16:9比例和最小高度
`; } generateStatsSection() { return `
📊 实时统计
`; } bindEvents() { const panel = this.configPanel; // 关闭按钮 panel.querySelector('.wo-config-close').addEventListener('click', () => { this.hide(); }); // 拖拽功能 const header = panel.querySelector('.wo-config-header'); header.addEventListener('mousedown', (e) => this.startDrag(e)); // 折叠/展开功能 panel.querySelectorAll('.wo-section-header').forEach(header => { header.addEventListener('click', (e) => { const section = e.currentTarget.dataset.section; this.toggleSection(section); }); }); // 配置项监听 panel.addEventListener('change', (e) => this.handleConfigChange(e)); panel.addEventListener('input', debounce((e) => this.handleConfigChange(e), 500)); // 操作按钮 panel.querySelector('[data-action="save"]').addEventListener('click', () => this.saveConfig()); panel.querySelector('[data-action="reset"]').addEventListener('click', () => this.resetConfig()); panel.querySelector('[data-action="export"]').addEventListener('click', () => this.exportConfig()); // 定期更新统计 setInterval(() => this.updateStats(), 2000); } startDrag(e) { this.isDragging = true; const rect = this.configPanel.getBoundingClientRect(); this.dragOffset.x = e.clientX - rect.left; this.dragOffset.y = e.clientY - rect.top; const handleMouseMove = (e) => { if (!this.isDragging) return; const x = e.clientX - this.dragOffset.x; const y = e.clientY - this.dragOffset.y; // 限制在视窗内 const maxX = window.innerWidth - this.configPanel.offsetWidth; const maxY = window.innerHeight - this.configPanel.offsetHeight; this.configPanel.style.left = Math.max(0, Math.min(x, maxX)) + 'px'; this.configPanel.style.top = Math.max(0, Math.min(y, maxY)) + 'px'; this.configPanel.style.right = 'auto'; }; const handleMouseUp = () => { this.isDragging = false; document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); }; document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); } toggleSection(sectionName) { const content = this.configPanel.querySelector(`[data-content="${sectionName}"]`); const toggle = this.configPanel.querySelector(`[data-section="${sectionName}"] .wo-section-toggle`); if (content.classList.contains('collapsed')) { content.classList.remove('collapsed'); toggle.classList.remove('collapsed'); toggle.textContent = '▼'; } else { content.classList.add('collapsed'); toggle.classList.add('collapsed'); toggle.textContent = '▶'; } } handleConfigChange(e) { const path = e.target.dataset.path; if (!path) return; let value; if (e.target.type === 'checkbox') { value = e.target.checked; } else if (e.target.type === 'number') { value = parseInt(e.target.value) || 0; } else if (e.target.className.includes('wo-config-textarea') && (path.includes('blacklist') || path.includes('whitelist') || path.includes('globalBlacklist'))) { value = e.target.value.split('\n').map(line => line.trim()).filter(line => line); } else if (path === 'preload.types') { value = e.target.value.split(',').map(type => type.trim()).filter(type => type); } else { value = e.target.value; } try { this.configManager.set(path, value); this.showNotification('配置已更新', 'success'); } catch (error) { this.showNotification('配置更新失败: ' + error.message, 'error'); } } saveConfig() { try { this.configManager.saveConfig(); this.showNotification('配置已保存到本地存储', 'success'); } catch (error) { this.showNotification('保存失败: ' + error.message, 'error'); } } resetConfig() { if (confirm('确定要重置为默认配置吗?这将丢失所有自定义设置。')) { try { // 重置配置 this.configManager.config = deepMerge({}, this.configManager.defaultConfig); this.configManager.saveConfig(); // 重新生成面板 this.configPanel.querySelector('.wo-config-content').innerHTML = this.generateGeneralSection(this.configManager.config) + this.generateLazyLoadSection(this.configManager.config) + this.generatePreconnectSection(this.configManager.config) + this.generatePreloadSection(this.configManager.config) + this.generateLayoutSection(this.configManager.config) + this.generateStatsSection(); // 重新绑定事件 this.bindEvents(); this.showNotification('配置已重置为默认值', 'success'); } catch (error) { this.showNotification('重置失败: ' + error.message, 'error'); } } } exportConfig() { try { const config = JSON.stringify(this.configManager.config, null, 2); const blob = new Blob([config], { type: 'application/json' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `web-optimizer-config-${new Date().toISOString().slice(0, 10)}.json`; link.click(); URL.revokeObjectURL(url); this.showNotification('配置已导出到文件', 'success'); } catch (error) { this.showNotification('导出失败: ' + error.message, 'error'); } } updateStats() { if (!this.isVisible) return; const statsGrid = document.getElementById('wo-stats-grid'); if (!statsGrid) return; try { const optimizer = window.WebOptimizer; if (!optimizer) return; const stats = optimizer.getPerformanceReport(); const counters = stats.counters; statsGrid.innerHTML = `
${document.querySelectorAll('img').length}
页面图片总数
${counters['processed-images']}
已处理图片
${counters['lazy-loaded-images']}
懒加载图片
${counters['preconnected-domains']}
预连接域名
${counters['preloaded-resources']}
预加载资源
${stats.performance.efficiency}
优化效率
`; } catch (error) { console.warn('更新统计数据失败:', error); } } showNotification(message, type = 'info') { // 创建通知元素 const notification = document.createElement('div'); notification.style.cssText = ` position: fixed; top: 20px; right: 20px; padding: 12px 20px; background: ${type === 'success' ? '#48bb78' : type === 'error' ? '#f56565' : '#4299e1'}; color: white; border-radius: 6px; font-size: 14px; z-index: 2147483648; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); transform: translateX(100%); transition: transform 0.3s ease; `; notification.textContent = message; document.body.appendChild(notification); // 显示动画 setTimeout(() => { notification.style.transform = 'translateX(0)'; }, 100); // 自动移除 setTimeout(() => { notification.style.transform = 'translateX(100%)'; setTimeout(() => { document.body.removeChild(notification); }, 300); }, 3000); } setupKeyboardShortcut() { // 创建浮动按钮 const toggleBtn = document.createElement('button'); toggleBtn.className = 'wo-toggle-btn'; toggleBtn.innerHTML = '⚙️'; toggleBtn.title = '打开 Web Optimizer 配置 (Ctrl+Shift+O)'; toggleBtn.addEventListener('click', () => this.toggle()); document.body.appendChild(toggleBtn); // 键盘快捷键 document.addEventListener('keydown', (e) => { if (e.ctrlKey && e.shiftKey && e.key === 'O') { e.preventDefault(); this.toggle(); } }); // 延迟显示浮动按钮 setTimeout(() => { toggleBtn.classList.remove('wo-hidden'); }, 2000); } show() { this.isVisible = true; this.configPanel.classList.remove('wo-hidden'); this.updateStats(); } hide() { this.isVisible = false; this.configPanel.classList.add('wo-hidden'); } toggle() { if (this.isVisible) { this.hide(); } else { this.show(); } } } // ======================== // 配置管理系统 // ======================== /** * 配置管理器 - 使用深度合并 */ class ConfigManager { constructor() { this.defaultConfig = { debug: false, // 全局黑名单 globalBlacklist: [ // 可以添加需要完全跳过优化的域名 ], // 懒加载配置 lazyLoad: { enabled: true, minSize: 100, rootMargin: '200px', threshold: 0.01, skipHidden: true, batchSize: 32, blacklist: [] }, // 预连接配置 preconnect: { enabled: true, maxConnections: 5, whitelist: [ // CDN 和字体服务 'fonts.gstatic.com', 'fonts.googleapis.com', 'fonts.googleapis.cn', 'fonts.loli.net', // 常用 CDN 'cdnjs.cloudflare.com', 'cdn.jsdelivr.net', 'unpkg.com', 'cdn.bootcdn.net', 'cdn.bootcss.com', 'libs.baidu.com', 'cdn.staticfile.org', // 其他常用服务 'ajax.googleapis.com', 'code.jquery.com', 'maxcdn.bootstrapcdn.com', 'kit.fontawesome.com', 'lf3-cdn-tos.bytecdntp.com', 'unpkg.zhimg.com', 'npm.elemecdn.com', 'g.alicdn.com' ], blacklist: [] }, // 预加载配置 preload: { enabled: true, maxPreloads: 5, types: ['css', 'js', 'woff2', 'woff'], fetchTimeout: 5000, retryAttempts: 3, blacklist: [] }, // 布局稳定性配置 layout: { enabled: true, stableImages: true, stableIframes: true, blacklist: [] } }; this.config = this.loadConfig(); this.validateConfig(); } loadConfig() { try { const saved = GM_getValue('optimizer_config_v2', null); if (saved) { // 使用深度合并替代浅合并 return deepMerge(this.defaultConfig, JSON.parse(saved)); } } catch (e) { console.warn('[配置] 加载用户配置失败,使用默认配置', e); } return deepMerge({}, this.defaultConfig); } saveConfig() { try { GM_setValue('optimizer_config_v2', JSON.stringify(this.config)); } catch (e) { console.warn('[配置] 保存配置失败', e); } } validateConfig() { // 基本类型验证 if (typeof this.config.debug !== 'boolean') { this.config.debug = this.defaultConfig.debug; } // 确保数组类型配置正确 ['globalBlacklist'].forEach(key => { if (!Array.isArray(this.config[key])) { this.config[key] = [...this.defaultConfig[key]]; } }); // 验证子配置 ['lazyLoad', 'preconnect', 'preload', 'layout'].forEach(module => { if (!this.config[module] || typeof this.config[module] !== 'object') { this.config[module] = deepMerge({}, this.defaultConfig[module]); } }); } get(path) { const keys = path.split('.'); let value = this.config; for (const key of keys) { value = value?.[key]; if (value === undefined) break; } return value; } set(path, value) { const keys = path.split('.'); const lastKey = keys.pop(); let target = this.config; for (const key of keys) { if (!target[key] || typeof target[key] !== 'object') { target[key] = {}; } target = target[key]; } target[lastKey] = value; this.saveConfig(); } isBlacklisted(hostname, module = null) { // 检查全局黑名单 if (this.config.globalBlacklist.some(domain => hostname.includes(domain))) { return true; } // 检查模块特定黑名单 if (module && this.config[module]?.blacklist) { return this.config[module].blacklist.some(domain => hostname.includes(domain)); } return false; } } // ======================== // 核心优化模块 // ======================== /** * 懒加载管理器 - 增强类型检查 */ class LazyLoadManager { constructor(config, monitor) { this.config = config; this.monitor = monitor; this.observer = null; this.mutationObserver = null; this.processedImages = new Set(); this.pendingImages = []; this.batchScheduled = false; this.processedElements = new WeakSet(); // 避免重复处理 } init() { if (!this.config.get('lazyLoad.enabled')) { this.monitor.log('懒加载功能已禁用'); return; } if (this.config.isBlacklisted(location.hostname, 'lazyLoad')) { this.monitor.log('当前站点在懒加载黑名单中'); return; } this.monitor.start('lazyLoad-init'); this.setupIntersectionObserver(); this.processExistingImages(); this.setupMutationObserver(); this.monitor.end('lazyLoad-init'); } setupIntersectionObserver() { if (!('IntersectionObserver' in window)) { this.monitor.warn('浏览器不支持 IntersectionObserver,使用兼容模式'); this.setupFallbackMode(); return; } this.observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { this.restoreImage(entry.target); this.observer.unobserve(entry.target); this.monitor.count('lazy-loaded-images'); } }); }, { rootMargin: this.config.get('lazyLoad.rootMargin'), threshold: this.config.get('lazyLoad.threshold') }); } setupFallbackMode() { const checkVisible = throttle(() => { const images = document.querySelectorAll('img[data-lazy-src]'); const margin = parseInt(this.config.get('lazyLoad.rootMargin')) || 200; images.forEach(img => { const rect = img.getBoundingClientRect(); if (rect.top < window.innerHeight + margin) { this.restoreImage(img); } }); }, 200); window.addEventListener('scroll', checkVisible, { passive: true }); window.addEventListener('resize', checkVisible, { passive: true }); checkVisible(); } isLazyCandidate(img) { // 基本检查 - 使用更严格的类型检查 if (!isImageElement(img)) return false; if (this.processedElements.has(img)) return false; if (img.hasAttribute('data-lazy-processed')) return false; if (img.loading === 'eager') return false; if (img.complete && img.src) return false; if (img.src && img.src.startsWith('data:')) return false; // 跳过已有懒加载的图片 if (img.hasAttribute('data-src') || img.hasAttribute('data-srcset')) return false; // 尺寸检查 const minSize = this.config.get('lazyLoad.minSize'); const rect = img.getBoundingClientRect(); if (rect.width < minSize || rect.height < minSize) return false; // 可见性检查 if (this.config.get('lazyLoad.skipHidden') && !isElementVisible(img)) { return false; } return true; } processImage(img) { if (!this.isLazyCandidate(img)) return false; // 标记为已处理 this.processedElements.add(img); img.setAttribute('data-lazy-processed', 'true'); // 设置原生懒加载(如果支持) if ('loading' in HTMLImageElement.prototype) { img.loading = 'lazy'; } // 保存原始 src if (img.src) { img.setAttribute('data-lazy-src', img.src); img.removeAttribute('src'); } if (img.srcset) { img.setAttribute('data-lazy-srcset', img.srcset); img.removeAttribute('srcset'); } // 添加到观察者 if (this.observer) { this.observer.observe(img); } this.processedImages.add(img); this.monitor.count('processed-images'); return true; } restoreImage(img) { const src = img.getAttribute('data-lazy-src'); const srcset = img.getAttribute('data-lazy-srcset'); if (src) { img.src = src; img.removeAttribute('data-lazy-src'); } if (srcset) { img.srcset = srcset; img.removeAttribute('data-lazy-srcset'); } this.processedImages.delete(img); } batchProcess(images) { const batchSize = this.config.get('lazyLoad.batchSize'); let processed = 0; const processBatch = () => { const end = Math.min(processed + batchSize, images.length); for (let i = processed; i < end; i++) { this.processImage(images[i]); } processed = end; if (processed < images.length) { (window.requestIdleCallback || window.requestAnimationFrame)(processBatch); } else { this.monitor.log(`懒加载处理完成,共处理 ${processed} 张图片`); } }; processBatch(); } processExistingImages() { const images = Array.from(document.querySelectorAll('img')); this.monitor.log(`发现 ${images.length} 张图片,开始批量处理`); this.batchProcess(images); } // 改进的批处理调度 - 更好的并发控制 scheduleBatchProcess() { if (this.batchScheduled || this.pendingImages.length === 0) return; this.batchScheduled = true; (window.requestIdleCallback || window.requestAnimationFrame)(() => { const images = [...this.pendingImages]; this.pendingImages = []; this.batchScheduled = false; let processedCount = 0; images.forEach(img => { if (this.processImage(img)) { processedCount++; } }); if (processedCount > 0) { this.monitor.log(`动态处理 ${processedCount} 张新图片`); } }); } setupMutationObserver() { let pendingMutations = []; let processingScheduled = false; const processMutations = () => { const mutations = [...pendingMutations]; pendingMutations = []; processingScheduled = false; mutations.forEach(mutation => { // 处理新增节点 mutation.addedNodes.forEach(node => { if (isImageElement(node)) { this.pendingImages.push(node); } else if (node.querySelectorAll) { const images = node.querySelectorAll('img'); this.pendingImages.push(...Array.from(images)); } }); // 清理移除的节点 mutation.removedNodes.forEach(node => { if (isImageElement(node) && this.observer) { this.observer.unobserve(node); this.processedImages.delete(node); this.processedElements.delete && this.processedElements.delete(node); } }); }); this.scheduleBatchProcess(); }; this.mutationObserver = new MutationObserver((mutations) => { pendingMutations.push(...mutations); if (!processingScheduled) { processingScheduled = true; (window.requestIdleCallback || window.requestAnimationFrame)(processMutations); } }); this.mutationObserver.observe(document.body, { childList: true, subtree: true }); } destroy() { if (this.observer) { this.observer.disconnect(); this.observer = null; } if (this.mutationObserver) { this.mutationObserver.disconnect(); this.mutationObserver = null; } // 恢复所有处理过的图片 this.processedImages.forEach(img => this.restoreImage(img)); this.processedImages.clear(); } } /** * 预加载管理器 - 改进异步处理 */ class PreloadManager { constructor(config, monitor) { this.config = config; this.monitor = monitor; this.preloaded = new LRUCache(this.config.get('preload.maxPreloads')); this.cssCache = new LRUCache(50); this.mutationObserver = null; } async init() { if (!this.config.get('preload.enabled')) { this.monitor.log('预加载功能已禁用'); return; } if (this.config.isBlacklisted(location.hostname, 'preload')) { this.monitor.log('当前站点在预加载黑名单中'); return; } this.monitor.start('preload-init'); await this.scanExistingResources(); // 改为异步 this.setupMutationObserver(); this.monitor.end('preload-init'); } getResourceType(url) { const ext = url.split('.').pop()?.toLowerCase(); const types = this.config.get('preload.types'); if (!types.includes(ext)) return null; switch (ext) { case 'css': return 'style'; case 'js': return 'script'; case 'woff': case 'woff2': case 'ttf': case 'otf': return 'font'; default: return null; } } doPreload(url, asType) { if (this.preloaded.has(url) || this.preloaded.size >= this.config.get('preload.maxPreloads')) { return false; } try { const link = document.createElement('link'); link.rel = 'preload'; link.as = asType; link.href = url; if (asType === 'font') { link.crossOrigin = 'anonymous'; // 设置正确的 MIME 类型 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'; } document.head.appendChild(link); this.preloaded.set(url, true); this.monitor.log(`预加载 ${asType}: ${url}`); this.monitor.count('preloaded-resources'); return true; } catch (e) { this.monitor.warn(`预加载失败: ${url}`, e); return false; } } async extractFontsFromCSS(cssUrl) { if (this.cssCache.has(cssUrl)) { return this.cssCache.get(cssUrl); } const operation = async () => { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.config.get('preload.fetchTimeout')); try { const response = await fetch(cssUrl, { signal: controller.signal, mode: 'cors', credentials: 'omit' }); clearTimeout(timeoutId); if (!response.ok) throw new Error(`HTTP ${response.status}`); const text = await response.text(); const fontUrls = []; const fontRegex = /url\(["']?([^")']+\.(woff2?|ttf|otf))["']?\)/gi; let match; while ((match = fontRegex.exec(text)) !== null) { const fontUrl = safeParseURL(match[1], cssUrl); if (fontUrl) { fontUrls.push(fontUrl.href); } } this.cssCache.set(cssUrl, fontUrls); return fontUrls; } finally { clearTimeout(timeoutId); } }; try { // 使用重试机制 return await RetryableOperation.executeWithRetry( operation, this.config.get('preload.retryAttempts') ); } catch (e) { this.monitor.warn(`提取字体失败: ${cssUrl}`, e.message); this.cssCache.set(cssUrl, []); return []; } } // 改进的异步资源扫描 - 更好的并发控制 async scanExistingResources() { // 处理 CSS 文件 const cssLinks = Array.from(document.querySelectorAll('link[rel="stylesheet"][href]')); const jsScripts = Array.from(document.querySelectorAll('script[src]')); // 处理 CSS 文件的 Promise 数组 const cssPromises = cssLinks.map(async link => { const cssUrl = link.href; const asType = this.getResourceType(cssUrl); if (asType === 'style') { this.doPreload(cssUrl, asType); // 异步提取和预加载字体 try { const fontUrls = await this.extractFontsFromCSS(cssUrl); fontUrls.forEach(fontUrl => { const fontType = this.getResourceType(fontUrl); if (fontType === 'font') { this.doPreload(fontUrl, fontType); } }); } catch (e) { // 忽略字体提取错误,不影响主流程 this.monitor.warn(`CSS处理失败: ${cssUrl}`, e); } } }); // 处理 JS 文件 jsScripts.forEach(script => { const asType = this.getResourceType(script.src); if (asType === 'script') { this.doPreload(script.src, asType); } }); // 等待所有 CSS 处理完成,但不阻塞初始化 try { await Promise.allSettled(cssPromises); this.monitor.log(`资源扫描完成,处理了 ${cssLinks.length} 个CSS文件和 ${jsScripts.length} 个JS文件`); } catch (e) { this.monitor.warn('资源扫描过程中出现错误', e); } } setupMutationObserver() { this.mutationObserver = new MutationObserver(debounce(async (mutations) => { const newCSSLinks = []; const newJSScripts = []; mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.tagName === 'LINK' && node.rel === 'stylesheet' && node.href) { newCSSLinks.push(node); } else if (node.tagName === 'SCRIPT' && node.src) { newJSScripts.push(node); } }); }); // 异步处理新添加的资源 if (newCSSLinks.length > 0 || newJSScripts.length > 0) { const promises = newCSSLinks.map(async node => { const asType = this.getResourceType(node.href); if (asType === 'style') { this.doPreload(node.href, asType); // 异步处理字体 try { const fontUrls = await this.extractFontsFromCSS(node.href); fontUrls.forEach(fontUrl => { const fontType = this.getResourceType(fontUrl); if (fontType === 'font') { this.doPreload(fontUrl, fontType); } }); } catch (e) { // 忽略错误 } } }); newJSScripts.forEach(node => { const asType = this.getResourceType(node.src); if (asType === 'script') { this.doPreload(node.src, asType); } }); // 不等待 Promise 完成,避免阻塞 Promise.allSettled(promises).then(() => { this.monitor.log(`动态处理了 ${newCSSLinks.length} 个CSS和 ${newJSScripts.length} 个JS`); }); } }, 200)); this.mutationObserver.observe(document.body, { childList: true, subtree: true }); } destroy() { if (this.mutationObserver) { this.mutationObserver.disconnect(); this.mutationObserver = null; } this.preloaded.clear(); this.cssCache.clear(); } } // 其他管理器类保持不变,仅引用已改进的配置和监控器 class PreconnectManager { constructor(config, monitor) { this.config = config; this.monitor = monitor; this.connected = new LRUCache(this.config.get('preconnect.maxConnections')); this.mutationObserver = null; } init() { if (!this.config.get('preconnect.enabled')) { this.monitor.log('预连接功能已禁用'); return; } if (this.config.isBlacklisted(location.hostname, 'preconnect')) { this.monitor.log('当前站点在预连接黑名单中'); return; } this.monitor.start('preconnect-init'); this.scanExistingResources(); this.setupMutationObserver(); this.monitor.end('preconnect-init'); } shouldPreconnect(hostname) { if (!hostname || hostname === location.hostname) return false; if (this.connected.has(hostname)) return false; const whitelist = this.config.get('preconnect.whitelist'); return whitelist.some(domain => hostname.endsWith(domain)); } doPreconnect(hostname) { if (!this.shouldPreconnect(hostname)) return false; try { const link = document.createElement('link'); link.rel = 'preconnect'; link.href = `https://${hostname}`; link.crossOrigin = 'anonymous'; document.head.appendChild(link); this.connected.set(hostname, true); this.monitor.log(`预连接: ${hostname}`); this.monitor.count('preconnected-domains'); return true; } catch (e) { this.monitor.warn(`预连接失败: ${hostname}`, e); return false; } } extractHostnames(elements) { const hostnames = new Set(); elements.forEach(el => { const url = safeParseURL(el.src || el.href); if (url && url.hostname !== location.hostname) { hostnames.add(url.hostname); } }); return Array.from(hostnames); } scanExistingResources() { const selectors = [ 'script[src]', 'link[href]', 'img[src]', 'audio[src]', 'video[src]', 'source[src]' ]; const elements = document.querySelectorAll(selectors.join(',')); const hostnames = this.extractHostnames(elements); let connected = 0; hostnames.forEach(hostname => { if (this.doPreconnect(hostname)) { connected++; } }); this.monitor.log(`扫描完成,预连接 ${connected} 个域名`); } setupMutationObserver() { this.mutationObserver = new MutationObserver(debounce((mutations) => { const newElements = []; mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.src || node.href) { newElements.push(node); } else if (node.querySelectorAll) { const elements = node.querySelectorAll('script[src], link[href], img[src]'); newElements.push(...elements); } }); }); if (newElements.length > 0) { const hostnames = this.extractHostnames(newElements); hostnames.forEach(hostname => this.doPreconnect(hostname)); } }, 200)); this.mutationObserver.observe(document.body, { childList: true, subtree: true }); } destroy() { if (this.mutationObserver) { this.mutationObserver.disconnect(); this.mutationObserver = null; } this.connected.clear(); } } class LayoutStabilizer { constructor(config, monitor) { this.config = config; this.monitor = monitor; this.injectedStyle = null; } init() { if (!this.config.get('layout.enabled')) { this.monitor.log('布局优化功能已禁用'); return; } if (this.config.isBlacklisted(location.hostname, 'layout')) { this.monitor.log('当前站点在布局优化黑名单中'); return; } this.monitor.start('layout-init'); this.injectStabilizationStyles(); this.monitor.end('layout-init'); } generateCSS() { const styles = []; if (this.config.get('layout.stableImages')) { styles.push(` /* 图片布局稳定性优化 */ img:not([width]):not([height]):not([style*="width"]):not([style*="height"]) { min-height: 1px; // max-width: 100%; // height: auto; } /* 现代浏览器的 aspect-ratio 支持 */ @supports (aspect-ratio: 1/1) { img[width][height]:not([style*="aspect-ratio"]) { aspect-ratio: attr(width) / attr(height); } } `); } if (this.config.get('layout.stableIframes')) { styles.push(` /* iframe 布局稳定性优化 */ iframe:not([width]):not([height]):not([style*="width"]):not([style*="height"]) { // width: 100%; // height: auto; min-height: 1px; } @supports (aspect-ratio: 16/9) { iframe:not([style*="aspect-ratio"]) { aspect-ratio: 16/9; } } `); } return styles.join('\n'); } injectStabilizationStyles() { const css = this.generateCSS().trim(); if (!css) return; try { this.injectedStyle = document.createElement('style'); this.injectedStyle.setAttribute('data-optimizer', 'layout-stabilizer'); this.injectedStyle.textContent = css; // 优先插入到 head,如果不存在则插入到 document const target = document.head || document.documentElement; target.appendChild(this.injectedStyle); this.monitor.log('布局稳定性样式已注入'); } catch (e) { this.monitor.warn('注入布局样式失败', e); } } destroy() { if (this.injectedStyle && this.injectedStyle.parentNode) { this.injectedStyle.parentNode.removeChild(this.injectedStyle); this.injectedStyle = null; this.monitor.log('布局样式已移除'); } } } // ======================== // 主优化器 // ======================== /** * 主优化器类 - v2.0 */ class WebOptimizer { constructor() { this.config = new ConfigManager(); this.monitor = new PerformanceMonitor(this.config.get('debug')); // 优化模块 this.modules = { lazyLoad: new LazyLoadManager(this.config, this.monitor), preconnect: new PreconnectManager(this.config, this.monitor), preload: new PreloadManager(this.config, this.monitor), layout: new LayoutStabilizer(this.config, this.monitor) }; this.initialized = false; this.cleanupTasks = []; // v2.0 新增:可视化配置界面 this.visualConfig = null; } async init() { if (this.initialized) return; this.monitor.start('total-init'); this.monitor.log('Web Optimizer Enhanced v2.0 开始初始化'); // 检查全局黑名单 if (this.config.isBlacklisted(location.hostname)) { this.monitor.log('当前站点在全局黑名单中,跳过所有优化'); return; } try { // 等待 DOM 基本可用 if (document.readyState === 'loading') { await new Promise(resolve => { document.addEventListener('DOMContentLoaded', resolve, { once: true }); }); } // 初始化各个模块 - 改进的错误隔离 const initPromises = Object.entries(this.modules).map(async ([name, module]) => { try { this.monitor.start(`init-${name}`); await module.init(); this.monitor.end(`init-${name}`); return { name, success: true }; } catch (e) { this.monitor.error(`模块 ${name} 初始化失败`, e); return { name, success: false, error: e }; } }); const results = await Promise.allSettled(initPromises); const successCount = results.filter(r => r.status === 'fulfilled' && r.value.success).length; this.monitor.log(`模块初始化完成,成功: ${successCount}/${Object.keys(this.modules).length}`); // 设置清理任务 this.setupCleanupTasks(); // v2.0 新增:初始化可视化配置界面 this.initVisualConfig(); this.initialized = true; this.monitor.end('total-init'); this.monitor.log('Web Optimizer Enhanced v2.0 初始化完成'); // 显示初始化报告 this.showInitReport(); } catch (e) { this.monitor.error('初始化失败', e); } } // v2.0 新增:初始化可视化配置界面 initVisualConfig() { try { // 延迟初始化,确保页面稳定 setTimeout(() => { this.visualConfig = new VisualConfigManager(this.config); }, 1000); } catch (e) { this.monitor.warn('可视化配置界面初始化失败', e); } } setupCleanupTasks() { // 定期清理缓存 const cleanupInterval = setInterval(() => { Object.values(this.modules).forEach(module => { if (module.cssCache) module.cssCache.clear(); if (module.connected) module.connected.clear(); if (module.preloaded) module.preloaded.clear(); }); this.monitor.log('定期清理完成'); }, 10 * 60 * 1000); // 10分钟 this.cleanupTasks.push(() => clearInterval(cleanupInterval)); // 页面卸载时清理 const cleanup = () => { this.destroy(); }; window.addEventListener('beforeunload', cleanup); this.cleanupTasks.push(() => { window.removeEventListener('beforeunload', cleanup); }); } showInitReport() { if (!this.config.get('debug')) return; console.groupCollapsed('[Web Optimizer Enhanced v2.0] 初始化报告'); console.log('版本: 2.0'); console.log('当前域名:', location.hostname); console.log('启用的功能:', Object.entries(this.config.config) .filter(([key, value]) => typeof value === 'object' && value.enabled) .map(([key]) => key) ); // 显示性能计数器 console.log('性能计数:', { 'processed-images': this.monitor.getCounter('processed-images'), 'lazy-loaded-images': this.monitor.getCounter('lazy-loaded-images'), 'preconnected-domains': this.monitor.getCounter('preconnected-domains'), 'preloaded-resources': this.monitor.getCounter('preloaded-resources') }); console.log('配置详情:', this.config.config); console.log('💡 提示: 按 Ctrl+Shift+O 打开可视化配置界面'); console.groupEnd(); } destroy() { if (!this.initialized) return; this.monitor.log('开始清理资源'); // 清理各个模块 Object.values(this.modules).forEach(module => { if (module.destroy) { try { module.destroy(); } catch (e) { this.monitor.warn('模块清理失败', e); } } }); // 执行清理任务 this.cleanupTasks.forEach(task => { try { task(); } catch (e) { this.monitor.warn('清理任务执行失败', e); } }); this.cleanupTasks = []; this.initialized = false; this.monitor.log('资源清理完成'); } // 公共 API - 增强版 updateConfig(path, value) { this.config.set(path, value); this.monitor.log(`配置已更新: ${path} = ${value}`); // 如果是调试模式变更,更新监控器 if (path === 'debug') { this.monitor.debug = value; } } getStats() { return { initialized: this.initialized, version: '2.0', hostname: location.hostname, config: this.config.config, modules: Object.keys(this.modules), counters: { 'processed-images': this.monitor.getCounter('processed-images'), 'lazy-loaded-images': this.monitor.getCounter('lazy-loaded-images'), 'preconnected-domains': this.monitor.getCounter('preconnected-domains'), 'preloaded-resources': this.monitor.getCounter('preloaded-resources') } }; } // 新增:性能报告 getPerformanceReport() { const stats = this.getStats(); const imageStats = { total: document.querySelectorAll('img').length, processed: stats.counters['processed-images'], lazyLoaded: stats.counters['lazy-loaded-images'] }; return { ...stats, performance: { images: imageStats, domains: stats.counters['preconnected-domains'], resources: stats.counters['preloaded-resources'], efficiency: imageStats.total > 0 ? (imageStats.processed / imageStats.total * 100).toFixed(1) + '%' : '0%' } }; } // v2.0 新增:打开/关闭配置界面 openConfig() { if (this.visualConfig) { this.visualConfig.show(); } } closeConfig() { if (this.visualConfig) { this.visualConfig.hide(); } } toggleConfig() { if (this.visualConfig) { this.visualConfig.toggle(); } } } // ======================== // 全局初始化 // ======================== // 创建全局实例 const optimizer = new WebOptimizer(); // 暴露到全局作用域(调试用) if (typeof window !== 'undefined') { window.WebOptimizer = optimizer; // v2.0 新增:暴露工具函数供调试使用 window.WebOptimizerUtils = { debounce, throttle, safeParseURL, isElementVisible, deepMerge, delay, RetryableOperation }; } // 启动优化器 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => optimizer.init()); } else { // 延迟一点时间,确保页面基本稳定 setTimeout(() => optimizer.init(), 100); } })();