// ==UserScript== // @name 替换网页字体 // @author ai // @version 2.62 // @include * // @run-at document-start // @early-start // @grant GM_getValues // @grant GM_setValues // @grant GM_addValueChangeListener // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @description 替换网页字体 // ==/UserScript== (function() { 'use strict'; const DEFAULT = { enabled: true, font: '微软雅黑', exclusions: 'code' }; class FontManager { constructor() { this.css = new CSSStyleSheet(); this.shadowdom = []; this.menus = []; this.init(); } init() { this.loadConfig(); this.addListener(); this.shadowRootHook(); this.refresh(); this.getWebfont(); this.updateMenu(); } async loadConfig() { this.config = GM_getValues(DEFAULT); await GM_setValues(this.config); } addListener() { Object.keys(DEFAULT).forEach(k => GM_addValueChangeListener(k, (key, oldV, newV) => { this.config[key] = newV; this.refresh(); if (key === 'enabled') this.updateMenu(); })); } shadowRootHook() { const self = this; Element.prototype.attachShadow = new Proxy(Element.prototype.attachShadow, { apply(target, thisArg, args) { const dom = Reflect.apply(target, thisArg, args); self.shadowdom.push(dom); self.config.enabled && self.applyCSS(dom); return dom; } }); } getWebfont() { const update = () => { const families = Array.from(document.fonts, f => `"${f.family}"`); const webFonts = [...new Set(families)].join(', '); if (webFonts !== this.webFonts) { this.webFonts = webFonts; this.refresh(); } }; document.fonts.ready.then(update); document.fonts.addEventListener('loadingdone', update); } refresh() { const { font, exclusions } = this.config; const fonts = this.webFonts ? `${font}, ${this.webFonts}` : font; const ex = `i, [class*="ico"], [class*="fa-"], [class*="symbol"]${exclusions ? ', ' + exclusions : ''}`; this.css.replaceSync(` @scope (:root, :host) to (${ex}) { * { font-family: ${fonts} !important; } } `); this.applyCSS(document); this.shadowdom.forEach(e => this.applyCSS(e)); } applyCSS(e) { const { enabled } = this.config; const currentSheets = [...e.adoptedStyleSheets].filter(s => s !== this.css); if (enabled) { e.adoptedStyleSheets = [...currentSheets, this.css]; } else { e.adoptedStyleSheets = currentSheets; } } updateMenu() { if (window.top !== window) return this.menus.forEach(GM_unregisterMenuCommand); this.menus = []; const reg = (txt, fn) => this.menus.push(GM_registerMenuCommand(txt, fn)); const set = async (k, v) => await GM_setValues({ [k]: v }); reg(this.config.enabled ? '❌ 禁用字体替换' : '✅ 启用字体替换', () => set('enabled', !this.config.enabled)); reg('🖋️ 配置字体名称', () => { const res = prompt('请输入字体名称', this.config.font); if (res) set('font', res); }); reg('🚫 配置排除元素', () => { const res = prompt('请输入排除的选择器', this.config.exclusions); if (res !== null) set('exclusions', res); }); reg('🗑️ 恢复默认值', async () => confirm('确定重置吗?') && await GM_setValues(DEFAULT)); } } new FontManager(); })();