Greasy Fork

网页字体替换增强器

替换网页默认字体,默认为 MiSans、FZYouSong GBK 509R(方正悠宋)、Cascadia Mono。可根据需求修改脚本中的字体设置。使用前需确保已安装所需字体。

目前为 2025-03-21 提交的版本。查看 最新版本

// ==UserScript==
// @name         网页字体替换增强器
// @namespace    chNt6w8D6cVSQE93BSC8VS6QxNshGaSP9QcK82kruzbN5E4K2TJKxbNjpAXDfJKe
// @description  替换网页默认字体,默认为 MiSans、FZYouSong GBK 509R(方正悠宋)、Cascadia Mono。可根据需求修改脚本中的字体设置。使用前需确保已安装所需字体。
// @version      7
// @license      Apache License 2.0
// @author       Anonymous
// @compatible   firefox
// @compatible   safari
// @compatible   chrome
// @match        *://*/*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @run-at       document-start
// ==/UserScript==
(function () {
    "use strict";
    const defaultConfig = {
        sans_namespace: ["MiSans", "MiSans L3", ""],
        sans_latin: ["MiSans", "MiSans L3", ""],
        sans_simplified: ["MiSans", "MiSans L3", ""],
        sans_traditional: ["MiSans", "MiSans L3", ""],
        serif_namespace: ["FZYouSong GBK 509R", "Times New Roman", ""],
        serif_simplified: ["FZYouSong GBK 509R", "Times New Roman", ""],
        serif_traditional: ["FZYouSong GBK 509R", "Times New Roman", ""],
        mono_namespace: ["Cascadia Mono", "Arial", ""],
        mono: ["Cascadia Mono", "Arial", ""],
    };
    function loadConfig() {
        return {
            sans_namespace: GM_getValue("sans_namespace", defaultConfig.sans_namespace),
            sans_latin: GM_getValue("sans_latin", defaultConfig.sans_latin),
            serif_namespace: GM_getValue("serif_namespace", defaultConfig.serif_namespace),
            sans_simplified: GM_getValue("sans_simplified", defaultConfig.sans_simplified),
            sans_traditional: GM_getValue("sans_traditional", defaultConfig.sans_traditional),
            serif_simplified: GM_getValue("serif_simplified", defaultConfig.serif_simplified),
            serif_traditional: GM_getValue("serif_traditional", defaultConfig.serif_traditional),
            mono_namespace: GM_getValue("mono_namespace", defaultConfig.mono_namespace),
            mono: GM_getValue("mono", defaultConfig.mono),
        };
    }
    function saveConfig() {
        Object.keys(config).forEach(key => {
            GM_setValue(key, config[key]);
        });
    }
    function createConfigUI() {
        const overlay = document.createElement("div");
        overlay.style = `
            position: fixed;
            inset: 0;
            background: rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(5px); /* 高斯模糊 */
            display: grid;
            place-items: center;
            z-index: 999999;
            transition: opacity 0.3s;
        `;
        const panel = document.createElement("div");
        panel.style = `
            background: #ffffff; /* 白色背景 */
            border-radius: 16px;
            width: min(95vw, 800px); /* 确保最大宽度不超过屏幕宽度 */
            max-height: 90vh;
            overflow-y: auto; /* 垂直滚动条 */
            padding: 2rem;
            box-shadow: 0 10px 25px rgba(0,0,0,0.1); /* 柔和阴影 */
            color: #333333; /* 文字颜色 */
            font-family: MiSans, sans-serif;
            animation: slideIn 0.3s ease;
        `;
        const title = document.createElement("h2");
        title.textContent = "字体替换增强器 · 配置面板";
        title.style = `
            margin: 0 0 1.5rem;
            font-size: 1.5rem;
            text-align: center;
            color: #333333; /* 深灰色标题 */
            position: relative;
            padding-bottom: 0.5rem;
        `;
        const titleUnderline = document.createElement("div");
        titleUnderline.style = `
            position: absolute;
            bottom: 0;
            left: 50%;
            transform: translateX(-50%);
            width: 60px;
            height: 3px;
            background: linear-gradient(90deg, #4f46e5, #ec4899);
            border-radius: 1px;
        `;
        title.appendChild(titleUnderline);
        const createInputGroup = (type, label) => {
            const container = document.createElement("div");
            container.style.marginBottom = "1.5rem";
            const titleLabel = document.createElement("label");
            titleLabel.textContent = label;
            titleLabel.style = `
                display: block;
                margin-bottom: 0.75rem;
                font-weight: 600;
                color: #333333; /* 深灰色标签 */
                font-size: 1rem;
                position: relative;
                padding-left: 1.25rem;
            `;
            titleLabel.innerHTML += `
                <span style="
                    position: absolute;
                    left: 0;
                    top: 50%;
                    transform: translateY(-50%);
                    width: 4px;
                    height: 1rem;
                    background: linear-gradient(transparent, #4f46e5, transparent);
                    border-radius: 2px;
                "></span>
            `;
            const inputContainer = document.createElement("div");
            inputContainer.style = `
                display: grid;
                grid-template-columns: repeat(3, calc(33.33% - 10px)); /* 每个输入框占三分之一宽度,减去间距 */
                gap: 10px; /* 输入框之间的间距 */
            `;
            for (let i = 0; i < 3; i++) {
                const input = document.createElement("input");
                input.type = "text";
                input.value = config[type][i];
                input.dataset.type = type;
                input.dataset.index = i;
                input.placeholder = `备选字体 ${i + 1}`;
                input.style = `
                    width: 100%; /* 输入框占满网格单元 */
                    padding: 0.75rem 1rem;
                    border: 1px solid #cccccc;
                    border-radius: 8px;
                    font-size: 0.75rem;
                    color: #333333; /* 深灰色文字 */
                    background: #f9f9f9; /* 浅灰色背景 */
                    transition: all 0.2s;
                `;
                input.addEventListener('focus', () => {
                    input.style.borderColor = '#4f46e5';
                    input.style.boxShadow = '0 0 0 2px rgba(79,70,229,0.2)';
                });
                input.addEventListener('blur', () => {
                    input.style.borderColor = '#cccccc';
                    input.style.boxShadow = 'none';
                });
                inputContainer.appendChild(input);
            }
            container.appendChild(titleLabel);
            container.appendChild(inputContainer);
            return container;
        };
        const createButton = (text, bgColor, hoverColor) => {
            const btn = document.createElement("button");
            btn.textContent = text;
            btn.style = `
                width: 100%;
                padding: 0.75rem 1rem;
                background: ${bgColor};
                border: none;
                border-radius: 8px;
                color: white;
                font-weight: 600;
                cursor: pointer;
                transition: all 0.2s;
                margin: 0.5rem 0;
            `;
            btn.addEventListener('mouseover', () => btn.style.backgroundColor = hoverColor);
            btn.addEventListener('mouseout', () => btn.style.backgroundColor = bgColor);
            return btn;
        };
        const importExportSection = document.createElement("div");
        importExportSection.style.margin = "1.5rem 0";
        const buttonContainer = document.createElement("div");
        buttonContainer.style.display = "flex";
        buttonContainer.style.gap = "0.75rem";
        const importInput = document.createElement("input");
        importInput.type = "file";
        importInput.accept = ".json";
        importInput.style.display = "none";
        const importBtn = createButton("导入配置", "#4f46e5", "#4338ca");
        importBtn.onclick = () => importInput.click();
        importInput.onchange = async () => {
            try {
                const file = importInput.files[0];
                const content = await file.text();
                const importedConfig = JSON.parse(content);
                Object.assign(config, importedConfig);
                saveConfig();
                alert("配置已导入,请刷新页面生效");
                overlay.remove();
            } catch (e) {
                alert("导入失败:无效的配置文件");
            }
        };
        const exportBtn = createButton("导出配置", "#16a34a", "#15803d");
        exportBtn.onclick = () => {
            const blob = new Blob([JSON.stringify(config, null, 2)], {
                type: "application/json",
            });
            const url = URL.createObjectURL(blob);
            const a = document.createElement("a");
            a.href = url;
            a.download = "font-config.json";
            a.click();
            URL.revokeObjectURL(url);
        };
        const saveBtn = createButton("保存配置", "#ec4899", "#be185d");
        saveBtn.style.marginTop = "1rem";
        saveBtn.onclick = () => {
            const inputs = panel.querySelectorAll("input");
            inputs.forEach(input => {
                config[input.dataset.type][input.dataset.index] = input.value;
            });
            saveConfig();
            alert("配置已保存,请刷新页面生效");
            overlay.remove();
        };
        const resetBtn = createButton("重置默认", "#f43f5e", "#e11d48");
        resetBtn.style.marginTop = "0.5rem";
        resetBtn.onclick = () => {
            if (confirm("确定要重置所有配置为默认值吗?")) {
                Object.keys(defaultConfig).forEach(key => {
                    config[key] = [...defaultConfig[key]];
                });
                saveConfig();
                overlay.remove();
                alert("配置已重置,请刷新页面生效");
            }
        };
        const closeBtn = document.createElement("button");
        closeBtn.innerHTML = "&times;";
        closeBtn.style = `
            position: absolute;
            top: 1rem;
            right: 1rem;
            background: none;
            border: none;
            color: #64748b;
            font-size: 1.5rem;
            cursor: pointer;
            transition: color 0.2s;
        `;
        closeBtn.addEventListener('click', () => overlay.remove());
        closeBtn.addEventListener('mouseover', () => closeBtn.style.color = '#f43f5e');
        closeBtn.addEventListener('mouseout', () => closeBtn.style.color = '#64748b');
        panel.appendChild(title);
        panel.appendChild(createInputGroup("sans_namespace", "无衬线 浏览器命名空间"));
        panel.appendChild(createInputGroup("sans_latin", "无衬线 拉丁文"));
        panel.appendChild(createInputGroup("sans_simplified", "无衬线 简化字形"));
        panel.appendChild(createInputGroup("sans_traditional", "无衬线 传统字形"));
        panel.appendChild(createInputGroup("serif_namespace", "衬线 浏览器命名空间"));
        panel.appendChild(createInputGroup("serif_simplified", "衬线 简化字形"));
        panel.appendChild(createInputGroup("serif_traditional", "衬线 传统字形"));
        panel.appendChild(createInputGroup("mono_namespace", "等宽字体 浏览器命名空间"));
        panel.appendChild(createInputGroup("mono", "等宽字体"));
        buttonContainer.appendChild(importBtn);
        buttonContainer.appendChild(exportBtn);
        importExportSection.appendChild(buttonContainer);
        panel.appendChild(importExportSection);
        panel.appendChild(saveBtn);
        panel.appendChild(resetBtn);
        panel.appendChild(closeBtn);
        overlay.appendChild(panel);
        document.body.appendChild(overlay);
    }
    function generateCSS() {
        const [sans_namespace_1, sans_namespace_2, sans_namespace_3] = config.sans_namespace;
        const [sans_latin_1, sans_latin_2, sans_latin_3] = config.sans_latin;
        const [sans_simplified_1, sans_simplified_2, sans_simplified_3] = config.sans_simplified;
        const [sans_traditional_1, sans_traditional_2, sans_traditional_3] = config.sans_traditional;
        const [serif_namespace_1, serif_namespace_2, serif_namespace_3] = config.serif_namespace;
        const [serif_simplified_1, serif_simplified_2, serif_simplified_3] = config.serif_simplified;
        const [serif_traditional_1, serif_traditional_2, serif_traditional_3] = config.serif_traditional;
        const [mono_namespace_1, mono_namespace_2, mono_namespace_3] = config.mono_namespace;
        const [mono_1, mono_2, mono_3] = config.mono;
        return `
            /* 浏览器命名空间 */
            @font-face{font-family:sans-serif;src:local(${sans_namespace_1}),local(${sans_namespace_2}),local(${sans_namespace_3});}
            @font-face{font-family:serif;src:local(${serif_namespace_1}),local(${serif_namespace_2}),local(${serif_namespace_3});}
            @font-face{font-family:monospace;src:local(${mono_namespace_1}),local(${mono_namespace_2}),local(${mono_namespace_3});}
            /* 无衬线字体 拉丁文 */
            @font-face{font-family:"Arial";src:local(${sans_latin_1}),local(${sans_latin_2}),local(${sans_latin_3});}
            @font-face{font-family:"Cambria";src:local(${sans_latin_1}),local(${sans_latin_2}),local(${sans_latin_3});}
            @font-face{font-family:"Calibri";src:local(${sans_latin_1}),local(${sans_latin_2}),local(${sans_latin_3});}
            @font-face{font-family:"Verdana";src:local(${sans_latin_1}),local(${sans_latin_2}),local(${sans_latin_3});}
            @font-face{font-family:"Helvetica";src:local(${sans_latin_1}),local(${sans_latin_2}),local(${sans_latin_3});}
            @font-face{font-family:"Helvetica Neue";src:local(${sans_latin_1}),local(${sans_latin_2}),local(${sans_latin_3});}
            @font-face{font-family:"HelveticaNeue";src:local(${sans_latin_1}),local(${sans_latin_2}),local(${sans_latin_3});}
            @font-face{font-family:"San Francisco";src:local(${sans_latin_1}),local(${sans_latin_2}),local(${sans_latin_3});}
            @font-face{font-family:"San Francisco Pro";src:local(${sans_latin_1}),local(${sans_latin_2}),local(${sans_latin_3});}
            @font-face{font-family:"Segoe UI";src:local(${sans_latin_1}),local(${sans_latin_2}),local(${sans_latin_3});}
            @font-face{font-family:"Google Sans";src:local(${sans_latin_1}),local(${sans_latin_2}),local(${sans_latin_3});}
            @font-face{font-family:"Google Sans Text";src:local(${sans_latin_1}),local(${sans_latin_2}),local(${sans_latin_3});}
            @font-face{font-family:"Roboto";src:local(${sans_latin_1}),local(${sans_latin_2}),local(${sans_latin_3});}
            @font-face{font-family:"Noto Sans";src:local(${sans_latin_1}),local(${sans_latin_2}),local(${sans_latin_3});}
            @font-face{font-family:"Lucida Sans";src:local(${sans_latin_1}),local(${sans_latin_2}),local(${sans_latin_3});}
            @font-face{font-family:"Lucida Grande";src:local(${sans_latin_1}),local(${sans_latin_2}),local(${sans_latin_3});}
            @font-face{font-family:"DejaVu Sans";src:local(${sans_latin_1}),local(${sans_latin_2}),local(${sans_latin_3});}
            @font-face{font-family:"Liberation Sans";src:local(${sans_latin_1}),local(${sans_latin_2}),local(${sans_latin_3});}
            @font-face{font-family:"Open Sans";src:local(${sans_latin_1}),local(${sans_latin_2}),local(${sans_latin_3});}
            /* 无衬线字体 简化字形 */
            @font-face{font-family:"HarmonyOS Sans";src:local(${sans_simplified_1}),local(${sans_simplified_2}),local(${sans_simplified_3});}
            @font-face{font-family:"Noto Sans SC";src:local(${sans_simplified_1}),local(${sans_simplified_2}),local(${sans_simplified_3});}
            @font-face{font-family:"SimHei";src:local(${sans_simplified_1}),local(${sans_simplified_2}),local(${sans_simplified_3});}
            @font-face{font-family:"黑体";src:local(${sans_simplified_1}),local(${sans_simplified_2}),local(${sans_simplified_3});}
            @font-face{font-family:"Microsoft YaHei";src:local(${sans_simplified_1}),local(${sans_simplified_2}),local(${sans_simplified_3});}
            @font-face{font-family:"微软雅黑";src:local(${sans_simplified_1}),local(${sans_simplified_2}),local(${sans_simplified_3});}
            @font-face{font-family:"Microsoft YaHei UI";src:local(${sans_simplified_1}),local(${sans_simplified_2}),local(${sans_simplified_3});}
            @font-face{font-family:"微软雅黑 UI";src:local(${sans_simplified_1}),local(${sans_simplified_2}),local(${sans_simplified_3});}
            @font-face{font-family:"PingFang SC";src:local(${sans_simplified_1}),local(${sans_simplified_2}),local(${sans_simplified_3});}
            @font-face{font-family:"Hiragino Sans GB";src:local(${sans_simplified_1}),local(${sans_simplified_2}),local(${sans_simplified_3});}
            @font-face{font-family:"STHeiti";src:local(${sans_simplified_1}),local(${sans_simplified_2}),local(${sans_simplified_3});}
            /* 无衬线字体 传统字形 */
            @font-face{font-family:"Noto Sans TC";src:local(${sans_traditional_1}),local(${sans_traditional_2}),local(${sans_traditional_3});}
            @font-face{font-family:"Microsoft JhengHei";src:local(${sans_traditional_1}),local(${sans_traditional_2}),local(${sans_traditional_3});}
            @font-face{font-family:"微軟正黑體";src:local(${sans_traditional_1}),local(${sans_traditional_2}),local(${sans_traditional_3});}
            @font-face{font-family:"Microsoft JhengHei UI";src:local(${sans_traditional_1}),local(${sans_traditional_2}),local(${sans_traditional_3});}
            @font-face{font-family:"MHei";src:local(${sans_traditional_1}),local(${sans_traditional_2}),local(${sans_traditional_3});}
            /* 衬线字体 简化字形 */
            @font-face{font-family:"SimSun";src:local(${serif_simplified_1}),local(${serif_simplified_2}),local(${serif_simplified_3});}
            @font-face{font-family:"宋体";src:local(${serif_simplified_1}),local(${serif_simplified_2}),local(${serif_simplified_3});}
            @font-face{font-family:"NSimSun";src:local(${serif_simplified_1}),local(${serif_simplified_2}),local(${serif_simplified_3});}
            @font-face{font-family:"新宋体";src:local(${serif_simplified_1}),local(${serif_simplified_2}),local(${serif_simplified_3});}
            @font-face{font-family:"FangSong";src:local(${serif_simplified_1}),local(${serif_simplified_2}),local(${serif_simplified_3});}
            @font-face{font-family:"FangSong_GB2312";src:local(${serif_simplified_1}),local(${serif_simplified_2}),local(${serif_simplified_3});}
            @font-face{font-family:"仿宋";src:local(${serif_simplified_1}),local(${serif_simplified_2}),local(${serif_simplified_3});}
            @font-face{font-family:"STSong";src:local(${serif_simplified_1}),local(${serif_simplified_2}),local(${serif_simplified_3});}
            @font-face{font-family:"STFangsong";src:local(${serif_simplified_1}),local(${serif_simplified_2}),local(${serif_simplified_3});}
            @font-face{font-family:"STZhongsong";src:local(${serif_simplified_1}),local(${serif_simplified_2}),local(${serif_simplified_3});}
            /* 等宽字体(代码) */
            @font-face{font-family:"Menlo";src:local(${mono_1}),local(${mono_2}),local(${mono_3});}
            @font-face{font-family:"Monaco";src:local(${mono_1}),local(${mono_2}),local(${mono_3});}
            @font-face{font-family:"Consolas";src:local(${mono_1}),local(${mono_2}),local(${mono_3});}
            @font-face{font-family:"Courier";src:local(${mono_1}),local(${mono_2}),local(${mono_3});}
            @font-face{font-family:"Courier New";src:local(${mono_1}),local(${mono_2}),local(${mono_3});}
            @font-face{font-family:"Andale Mono";src:local(${mono_1}),local(${mono_2}),local(${mono_3});}
            @font-face{font-family:"Ubuntu Mono";src:local(${mono_1}),local(${mono_2}),local(${mono_3});}
            @font-face{font-family:"Fira Code";src:local(${mono_1}),local(${mono_2}),local(${mono_3});}
            @font-face{font-family:"Fira Mono";src:local(${mono_1}),local(${mono_2}),local(${mono_3});}
            @font-face{font-family:"DejaVu Sans Mono";src:local(${mono_1}),local(${mono_2}),local(${mono_3});}
            @font-face{font-family:"Liberation Mono";src:local(${mono_1}),local(${mono_2}),local(${mono_3});}
            @font-face{font-family:"Source Code Pro";src:local(${mono_1}),local(${mono_2}),local(${mono_3});}
            /* 字体渲染优化 */
            body{-webkit-font-smoothing:subpixel-antialiased;-moz-osx-font-smoothing:grayscale;}
        `;
    }
    const config = loadConfig();
    GM_registerMenuCommand("配置面板", createConfigUI);
    GM_addStyle(generateCSS());
})();