网页字体替换增强器

替换网页默认字体,默认为 MiSans、TsangerJinKai05 W04、Source Han Serif SC、Cascadia Code。可根据需求修改脚本中的字体设置。使用前需确保已安装所需字体。

当前为 2025-04-12 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         网页字体替换增强器
// @namespace    chNt6w8D6cVSQE93BSC8VS6QxNshGaSP9QcK82kruzbN5E4K2TJKxbNjpAXDfJKe
// @description  替换网页默认字体,默认为 MiSans、TsangerJinKai05 W04、Source Han Serif SC、Cascadia Code。可根据需求修改脚本中的字体设置。使用前需确保已安装所需字体。
// @version      8
// @license      Apache License 2.0
// @author       Ckrvxr
// @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 TC", "MiSans L3", "MiSans"],
        serif_namespace: ["TsangerJinKai05 W04", "Source Han Serif SC", ""],
        serif_latin: ["TsangerJinKai05 W04", "Source Han Serif", ""],
        serif_simplified: ["TsangerJinKai05 W04", "Source Han Serif SC", ""],
        serif_traditional: ["TsangerJinKai05 W04", "Source Han Serif TC", "Source Han Serif SC"],
        mono_namespace: ["Cascadia Code", "Cascadia Mono", ""],
        mono_latin: ["Cascadia Code", "Cascadia Mono", ""],
    };

    // 加载配置
    function loadConfig() {
        return {
            sans_namespace: GM_getValue("sans_namespace", defaultConfig.sans_namespace),
            sans_latin: GM_getValue("sans_latin", defaultConfig.sans_latin),
            sans_simplified: GM_getValue("sans_simplified", defaultConfig.sans_simplified),
            sans_traditional: GM_getValue("sans_traditional", defaultConfig.sans_traditional),
            serif_namespace: GM_getValue("serif_namespace", defaultConfig.serif_namespace),
            serif_latin: GM_getValue("serif_latin", defaultConfig.serif_latin),
            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_latin: GM_getValue("mono_latin", defaultConfig.mono_latin),
        };
    }

    // 保存配置
    function saveConfig(config) {
        Object.keys(config).forEach((key) => {
            GM_setValue(key, config[key]);
        });
    }

    function createConfigUI(config) {

        document.body.style.overflow = "hidden";
        document.documentElement.style.overflowX = "hidden";
        document.documentElement.style.overflowY = "hidden";

        const panel = document.createElement("div");
        panel.id = "font-config-panel";
        panel.style.cssText = `
            position: fixed;
            inset: 0;
            background: #ffffff;
            overflow-y: auto;
            padding: 4rem;
            font-family: sans-serif;
            color: #333333;
            z-index: 999999;
            max-height: 100vh;
            box-sizing: border-box;
        `;

        const title = document.createElement("h1");
        title.textContent = "字体替换增强器 · 配置面板";
        title.style.cssText = `
            margin: 0 0 1rem;
            font-size: 1.5rem;
            text-align: center;
            color: #333333;
            position: relative;
        `;

        const titleUnderline = document.createElement("div");
        titleUnderline.style.cssText = `
            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 = "1rem";

            const titleLabel = document.createElement("label");
            titleLabel.textContent = label;
            titleLabel.style.cssText = `
                display: block;
                margin-bottom: 0.5rem;
                font-weight: 600;
                color: #333333;
                font-size: 1rem;
            `;

            const inputContainer = document.createElement("div");
            inputContainer.style.cssText = `
                display: grid;
                grid-template-columns: repeat(3, 1fr);
                gap: 2rem;
            `;

            config[type].forEach((value, index) => {
                const input = document.createElement("input");
                input.type = "text";
                input.value = value;
                input.dataset.type = type;
                input.dataset.index = index;
                input.placeholder = `备选字体 ${index + 1}`;
                input.style.cssText = `
                    width: 100%;
                    padding: 0.5rem;
                    border: 1px solid #cccccc;
                    border-radius: 6px;
                    font-size: 0.875rem;
                    color: #333333;
                    background: #f9f9f9;
                    transition: all 0.2s;
                `;
                inputContainer.appendChild(input);
            });

            container.appendChild(titleLabel);
            container.appendChild(inputContainer);
            return container;
        };

        const createButton = (text, colorScheme) => {
            const btn = document.createElement("button");
            btn.textContent = text;
            btn.style.cssText = `
                flex: 1;
                padding: 0.75rem;
                border: none;
                border-radius: 6px;
                font-weight: 600;
                cursor: pointer;
                transition: all 0.2s;
                margin: 0.5rem 0;
                color: white;
                background: ${colorScheme.base};
                box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            `;

            btn.addEventListener("mouseover", () => {
                btn.style.background = colorScheme.hover;
                btn.style.transform = "translateY(-1px)";
            });

            btn.addEventListener("mouseout", () => {
                btn.style.background = colorScheme.base;
                btn.style.transform = "translateY(0)";
            });

            return btn;
        };

        const colorSchemes = {
            primary: {
                base: "#4f46e5",
                hover: "#4338ca"
            },
            secondary: {
                base: "#3b82f6",
                hover: "#2563eb"
            },
            success: {
                base: "#10b981",
                hover: "#0b8a5e"
            },
            warning: {
                base: "#f59e0b",
                hover: "#d97706"
            },
            danger: {
                base: "#ef4444",
                hover: "#dc2626"
            },
            info: {
                base: "#64748b",
                hover: "#475569"
            }
        };

        const saveBtn = createButton("保存配置", colorSchemes.success);
        saveBtn.onclick = () => {
            const inputs = panel.querySelectorAll("input");
            inputs.forEach((input) => {
                config[input.dataset.type][input.dataset.index] = input.value;
            });
            saveConfig(config);
            alert("配置已保存,请刷新页面生效");
            panel.remove();
            document.body.style.overflow = "";
            document.documentElement.style.overflowX = "";
        };

        const closeBtn = createButton("关闭面板", colorSchemes.secondary);
        closeBtn.onclick = () => {
            panel.remove();
            document.body.style.overflow = "";
            document.documentElement.style.overflowX = "";
            document.documentElement.style.overflowY = "";
        };

        const importInput = document.createElement("input");
        importInput.type = "file";
        importInput.accept = ".json";
        importInput.style.display = "none";

        const importBtn = createButton("导入配置", colorSchemes.warning);
        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(config);
                alert("配置已导入,请刷新页面生效");
                panel.remove();
                document.body.style.overflow = "";
                document.documentElement.style.overflowX = "";
            } catch (e) {
                alert("导入失败:无效的配置文件");
            }
        };

        const exportBtn = createButton("导出配置", colorSchemes.info);
        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 resetBtn = createButton("重置默认", colorSchemes.danger);
        resetBtn.onclick = () => {
            if (confirm("确定要重置所有配置为默认值吗?")) {
                Object.keys(defaultConfig).forEach((key) => {
                    config[key] = [...defaultConfig[key]];
                });
                saveConfig(config);
                alert("配置已重置,请刷新页面生效");
                panel.remove();
                document.body.style.overflow = "";
                document.documentElement.style.overflowX = "";
            }
        };

        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_latin", "衬线 拉丁文"));
        panel.appendChild(createInputGroup("serif_simplified", "衬线 简化字形"));
        panel.appendChild(createInputGroup("serif_traditional", "衬线 传统字形"));
        panel.appendChild(createInputGroup("mono_namespace", "等宽 浏览器命名空间"));
        panel.appendChild(createInputGroup("mono_latin", "等宽 拉丁文"));

        const actionRow1 = document.createElement("div");
        actionRow1.style.cssText = `
            display: flex;
            gap: 0.75rem;
            margin: 1rem 0;
        `;
        actionRow1.appendChild(importBtn);
        actionRow1.appendChild(exportBtn);
        actionRow1.appendChild(resetBtn);
        panel.appendChild(actionRow1);

        const actionRow2 = document.createElement("div");
        actionRow2.style.cssText = `
            display: flex;
            gap: 0.75rem;
            margin: 1rem 0;
        `;
        actionRow2.appendChild(saveBtn);
        actionRow2.appendChild(closeBtn);
        panel.appendChild(actionRow2);

        document.body.appendChild(panel);
    }

    // 生成 CSS 样式
    function generateCSS(config) {
        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_latin_1, serif_latin_2, serif_latin_3] = config.serif_latin;
        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_latin_1, mono_latin_2, mono_latin_3] = config.mono_latin;

        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:"Times New Roman";src:local(${serif_latin_1}),local(${serif_latin_2}),local(${serif_latin_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_latin_1}),local(${mono_latin_2}),local(${mono_latin_3});}
            @font-face{font-family:"Monaco";src:local(${mono_latin_1}),local(${mono_latin_2}),local(${mono_latin_3});}
            @font-face{font-family:"Consolas";src:local(${mono_latin_1}),local(${mono_latin_2}),local(${mono_latin_3});}
            @font-face{font-family:"Courier";src:local(${mono_latin_1}),local(${mono_latin_2}),local(${mono_latin_3});}
            @font-face{font-family:"Courier New";src:local(${mono_latin_1}),local(${mono_latin_2}),local(${mono_latin_3});}
            @font-face{font-family:"Andale Mono";src:local(${mono_latin_1}),local(${mono_latin_2}),local(${mono_latin_3});}
            @font-face{font-family:"Ubuntu Mono";src:local(${mono_latin_1}),local(${mono_latin_2}),local(${mono_latin_3});}
            @font-face{font-family:"Fira Code";src:local(${mono_latin_1}),local(${mono_latin_2}),local(${mono_latin_3});}
            @font-face{font-family:"Fira Mono";src:local(${mono_latin_1}),local(${mono_latin_2}),local(${mono_latin_3});}
            @font-face{font-family:"DejaVu Sans Mono";src:local(${mono_latin_1}),local(${mono_latin_2}),local(${mono_latin_3});}
            @font-face{font-family:"Liberation Mono";src:local(${mono_latin_1}),local(${mono_latin_2}),local(${mono_latin_3});}
            @font-face{font-family:"Source Code Pro";src:local(${mono_latin_1}),local(${mono_latin_2}),local(${mono_latin_3});}
            /* 字体渲染优化 */
            body{-webkit-font-smoothing:subpixel-antialiased;-moz-osx-font-smoothing:grayscale;}
        `;
    }

    const config = loadConfig();
    GM_registerMenuCommand("配置面板", () => createConfigUI(config));
    GM_addStyle(generateCSS(config));
})();