// ==UserScript==
// @name Ultimate Web Optimizer
// @namespace https://greasyfork.org/zh-CN/users/1474228-moyu001
// @version 1.2.2
// @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);
batchProcess(imgs, el => observer.observe(processMedia(el)), undefined, () => {
if (config.debug) {
console.log(`[懒加载] 初始图片: ${imgs.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' && 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') {
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();
}
});
})();