Skip to content

网页颜色自定义油猴脚本

一个轻量级的 Tampermonkey 脚本,允许你在任意网页上自定义背景色、文字颜色和链接颜色,并持久化保存设置。

脚本代码

javascript
// ==UserScript==
// @name         网页颜色自定义(带主题)
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  自定义网页背景色和文字颜色,支持预设主题
// @author       元宝
// @match        *://*/*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // 默认颜色设置
    const defaultColors = {
        bgColor: '#ffffff',
        textColor: '#000000',
        linkColor: '#0000ee'
    };

    // 预设主题
    const colorThemes = {
        'default': {
            name: '默认主题',
            bgColor: '#ffffff',
            textColor: '#000000',
            linkColor: '#0000ee'
        },
        'dark': {
            name: '暗黑模式',
            bgColor: '#121212',
            textColor: '#ffffff',
            linkColor: '#bb86fc'
        },
        'dark-blue': {
            name: '深蓝主题',
            bgColor: '#0a192f',
            textColor: '#ccd6f6',
            linkColor: '#64ffda'
        },
        'eye-protect': {
            name: '护眼模式',
            bgColor: '#c7edcc',
            textColor: '#000000',
            linkColor: '#1890ff'
        },
        'sepia': {
            name: '羊皮纸',
            bgColor: '#f4ecd8',
            textColor: '#5b4636',
            linkColor: '#9c4a1a'
        },
        'terminal': {
            name: '终端风格',
            bgColor: '#000000',
            textColor: '#00ff00',
            linkColor: '#00ffff'
        },
        'github-dark': {
            name: 'GitHub暗色',
            bgColor: '#0d1117',
            textColor: '#c9d1d9',
            linkColor: '#58a6ff'
        },
        'solarized-light': {
            name: 'Solarized浅色',
            bgColor: '#fdf6e3',
            textColor: '#657b83',
            linkColor: '#268bd2'
        },
        'solarized-dark': {
            name: 'Solarized深色',
            bgColor: '#002b36',
            textColor: '#839496',
            linkColor: '#2aa198'
        },
        'midnight': {
            name: '午夜蓝',
            bgColor: '#191970',
            textColor: '#e6e6fa',
            linkColor: '#9370db'
        },
        'warm': {
            name: '暖色调',
            bgColor: '#fffaf0',
            textColor: '#8b4513',
            linkColor: '#cd853f'
        }
    };

    // 从存储中获取颜色或使用默认值
    let currentTheme = GM_getValue('currentTheme', 'default');
    let bgColor = GM_getValue('bgColor', defaultColors.bgColor);
    let textColor = GM_getValue('textColor', defaultColors.textColor);
    let linkColor = GM_getValue('linkColor', defaultColors.linkColor);

    // 更新当前主题的颜色
    function updateColorsFromTheme(themeName) {
        const theme = colorThemes[themeName];
        if (theme) {
            bgColor = theme.bgColor;
            textColor = theme.textColor;
            linkColor = theme.linkColor;
            currentTheme = themeName;
            
            // 更新输入框
            document.getElementById('bgColorInput').value = bgColor;
            document.getElementById('textColorInput').value = textColor;
            document.getElementById('linkColorInput').value = linkColor;
            
            // 更新预览
            document.getElementById('bgPreview').style.backgroundColor = bgColor;
            document.getElementById('textPreview').style.backgroundColor = textColor;
            document.getElementById('linkPreview').style.backgroundColor = linkColor;
            
            // 更新主题选择器
            document.getElementById('themeSelect').value = themeName;
            
            // 保存设置
            GM_setValue('currentTheme', themeName);
            GM_setValue('bgColor', bgColor);
            GM_setValue('textColor', textColor);
            GM_setValue('linkColor', linkColor);
            
            // 应用颜色
            applyColors();
        }
    }

    // 添加自定义CSS样式
    GM_addStyle(`
        .color-customizer {
            position: fixed;
            top: 50%;
            right: 0;
            transform: translateY(-50%);
            background: rgba(255, 255, 255, 0.98);
            border: 1px solid #ccc;
            border-radius: 8px 0 0 8px;
            padding: 15px;
            box-shadow: -2px 0 10px rgba(0,0,0,0.1);
            z-index: 999999;
            min-width: 250px;
            font-family: Arial, sans-serif;
            max-height: 80vh;
            overflow-y: auto;
        }
        
        .color-customizer h3 {
            margin-top: 0;
            font-size: 16px;
            color: #333;
            padding-bottom: 10px;
            border-bottom: 1px solid #eee;
        }
        
        .color-control {
            margin: 12px 0;
        }
        
        .color-control label {
            display: block;
            font-size: 12px;
            margin-bottom: 5px;
            color: #666;
            font-weight: bold;
        }
        
        .color-preview {
            width: 20px;
            height: 20px;
            border: 1px solid #ccc;
            border-radius: 3px;
            display: inline-block;
            vertical-align: middle;
            margin-right: 5px;
            cursor: pointer;
        }
        
        .color-input {
            width: calc(100% - 30px);
            padding: 5px;
            border: 1px solid #ddd;
            border-radius: 3px;
            font-size: 12px;
        }
        
        .theme-select {
            width: 100%;
            padding: 6px;
            border: 1px solid #ddd;
            border-radius: 4px;
            background: white;
            font-size: 12px;
            margin-bottom: 10px;
        }
        
        .theme-select option {
            padding: 5px;
        }
        
        .theme-preview {
            display: inline-block;
            width: 12px;
            height: 12px;
            border-radius: 2px;
            margin-right: 8px;
            border: 1px solid #ddd;
        }
        
        .theme-item {
            display: flex;
            align-items: center;
            padding: 3px 0;
        }
        
        .color-btn {
            background: #4CAF50;
            color: white;
            border: none;
            padding: 8px 12px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
            margin-top: 5px;
            width: calc(50% - 3px);
            transition: background 0.3s;
        }
        
        .color-btn:hover {
            background: #45a049;
        }
        
        .color-btn.reset {
            background: #f44336;
        }
        
        .color-btn.reset:hover {
            background: #da190b;
        }
        
        .toggle-btn {
            position: fixed;
            top: 50%;
            right: 0;
            transform: translateY(-50%);
            background: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px 0 0 4px;
            padding: 8px 12px;
            cursor: pointer;
            z-index: 999998;
            font-size: 12px;
            box-shadow: -2px 0 5px rgba(0,0,0,0.1);
        }
        
        .toggle-btn:hover {
            background: #45a049;
        }
        
        .button-group {
            display: flex;
            justify-content: space-between;
            gap: 6px;
            margin-top: 10px;
        }
        
        .theme-section {
            margin: 15px 0;
            padding: 10px;
            background: #f9f9f9;
            border-radius: 5px;
            border-left: 3px solid #4CAF50;
        }
        
        .theme-section h4 {
            margin: 0 0 8px 0;
            font-size: 13px;
            color: #333;
        }
        
        .quick-themes {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 8px;
            margin-top: 5px;
        }
        
        .theme-btn {
            padding: 6px 8px;
            border: 1px solid #ddd;
            background: white;
            border-radius: 4px;
            cursor: pointer;
            font-size: 11px;
            text-align: center;
            transition: all 0.2s;
        }
        
        .theme-btn:hover {
            background: #f0f0f0;
            transform: translateY(-1px);
        }
        
        .theme-btn.active {
            background: #4CAF50;
            color: white;
            border-color: #45a049;
        }
        
        .theme-name {
            font-weight: bold;
        }
    `);

    // 创建颜色自定义面板
    function createColorPanel() {
        const panel = document.createElement('div');
        panel.className = 'color-customizer';
        panel.innerHTML = `
            <h3>🎨 网页颜色设置</h3>
            
            <div class="theme-section">
                <h4>预设主题</h4>
                <select id="themeSelect" class="theme-select">
                    ${Object.entries(colorThemes).map(([id, theme]) => `
                        <option value="${id}" ${id === currentTheme ? 'selected' : ''}>
                            ${theme.name}
                        </option>
                    `).join('')}
                </select>
                
                <div class="quick-themes">
                    <button class="theme-btn ${currentTheme === 'dark' ? 'active' : ''}" data-theme="dark" style="background: linear-gradient(45deg, #121212 50%, #333 50%); color: white;">
                        暗黑
                    </button>
                    <button class="theme-btn ${currentTheme === 'eye-protect' ? 'active' : ''}" data-theme="eye-protect" style="background: #c7edcc; color: #000;">
                        护眼
                    </button>
                    <button class="theme-btn ${currentTheme === 'dark-blue' ? 'active' : ''}" data-theme="dark-blue" style="background: #0a192f; color: #ccd6f6;">
                        深蓝
                    </button>
                    <button class="theme-btn ${currentTheme === 'sepia' ? 'active' : ''}" data-theme="sepia" style="background: #f4ecd8; color: #5b4636;">
                        羊皮纸
                    </button>
                </div>
            </div>
            
            <div class="color-control">
                <label>背景颜色:</label>
                <div class="color-preview" id="bgPreview" style="background-color: ${bgColor};"></div>
                <input type="text" id="bgColorInput" class="color-input" value="${bgColor}" placeholder="#ffffff 或 rgb(255,255,255)">
            </div>
            
            <div class="color-control">
                <label>文字颜色:</label>
                <div class="color-preview" id="textPreview" style="background-color: ${textColor};"></div>
                <input type="text" id="textColorInput" class="color-input" value="${textColor}" placeholder="#000000 或 rgb(0,0,0)">
            </div>
            
            <div class="color-control">
                <label>链接颜色:</label>
                <div class="color-preview" id="linkPreview" style="background-color: ${linkColor};"></div>
                <input type="text" id="linkColorInput" class="color-input" value="${linkColor}" placeholder="#0000ee 或 rgb(0,0,238)">
            </div>
            
            <div class="button-group">
                <button class="color-btn" id="applyColors">💾 保存并应用</button>
                <button class="color-btn reset" id="resetColors">🔄 重置默认</button>
            </div>
        `;
        
        return panel;
    }

    // 创建切换按钮
    function createToggleButton() {
        const toggleBtn = document.createElement('button');
        toggleBtn.className = 'toggle-btn';
        toggleBtn.textContent = '🎨 颜色';
        toggleBtn.title = '显示/隐藏颜色设置面板';
        return toggleBtn;
    }

    // 应用颜色到页面
    function applyColors() {
        const styleId = 'custom-colors-style';
        let styleEl = document.getElementById(styleId);
        
        if (!styleEl) {
            styleEl = document.createElement('style');
            styleEl.id = styleId;
            document.head.appendChild(styleEl);
        }
        
        const css = `
            body, div, section, article, main, header, footer, aside, nav {
                background-color: ${bgColor} !important;
                color: ${textColor} !important;
            }
            
            a, a:link, a:visited, a:hover, a:active {
                color: ${linkColor} !important;
            }
            
            /* 处理一些特殊元素 */
            input, textarea, select, button {
                background-color: inherit !important;
                color: ${textColor} !important;
            }
            
            /* 处理代码块和预格式文本 */
            pre, code, kbd, samp {
                background-color: inherit !important;
                color: ${textColor} !important;
            }
            
            /* 处理表格 */
            table, th, td {
                background-color: inherit !important;
                color: ${textColor} !important;
                border-color: ${textColor}33 !important;
            }
        `;
        
        styleEl.textContent = css;
        
        // 保存到存储
        GM_setValue('bgColor', bgColor);
        GM_setValue('textColor', textColor);
        GM_setValue('linkColor', linkColor);
        
        // 检查是否匹配某个预设主题
        let matchedTheme = 'default';
        for (const [themeId, theme] of Object.entries(colorThemes)) {
            if (theme.bgColor === bgColor && theme.textColor === textColor && theme.linkColor === linkColor) {
                matchedTheme = themeId;
                break;
            }
        }
        
        if (matchedTheme !== currentTheme) {
            currentTheme = matchedTheme;
            GM_setValue('currentTheme', matchedTheme);
            
            // 更新UI
            const themeSelect = document.getElementById('themeSelect');
            if (themeSelect) themeSelect.value = matchedTheme;
            
            // 更新快速主题按钮状态
            document.querySelectorAll('.theme-btn').forEach(btn => {
                btn.classList.remove('active');
                if (btn.dataset.theme === matchedTheme) {
                    btn.classList.add('active');
                }
            });
        }
    }

    // 重置颜色
    function resetColors() {
        updateColorsFromTheme('default');
    }

    // 初始化
    function init() {
        // 创建面板和按钮
        const panel = createColorPanel();
        const toggleBtn = createToggleButton();
        
        document.body.appendChild(panel);
        document.body.appendChild(toggleBtn);
        
        // 应用初始颜色
        setTimeout(applyColors, 100);
        
        // 切换面板显示/隐藏
        let panelVisible = true;
        toggleBtn.addEventListener('click', () => {
            panelVisible = !panelVisible;
            panel.style.display = panelVisible ? 'block' : 'none';
            toggleBtn.textContent = panelVisible ? '🎨 颜色' : '🎨';
        });
        
        // 主题选择器事件
        document.getElementById('themeSelect').addEventListener('change', function(e) {
            updateColorsFromTheme(e.target.value);
        });
        
        // 快速主题按钮事件
        document.querySelectorAll('.theme-btn').forEach(btn => {
            btn.addEventListener('click', function() {
                updateColorsFromTheme(this.dataset.theme);
            });
        });
        
        // 颜色预览点击事件
        const previews = ['bgPreview', 'textPreview', 'linkPreview'];
        previews.forEach(previewId => {
            const preview = document.getElementById(previewId);
            preview.addEventListener('click', () => {
                const colorInput = previewId.replace('Preview', 'Input');
                const input = document.getElementById(colorInput);
                
                // 创建一个临时的颜色选择器
                const colorPicker = document.createElement('input');
                colorPicker.type = 'color';
                colorPicker.value = input.value;
                colorPicker.style.position = 'fixed';
                colorPicker.style.left = '-1000px';
                document.body.appendChild(colorPicker);
                
                colorPicker.click();
                
                colorPicker.addEventListener('change', () => {
                    const newColor = colorPicker.value;
                    input.value = newColor;
                    preview.style.backgroundColor = newColor;
                    
                    // 更新对应的颜色变量
                    if (previewId === 'bgPreview') bgColor = newColor;
                    if (previewId === 'textPreview') textColor = newColor;
                    if (previewId === 'linkPreview') linkColor = newColor;
                    
                    // 标记为自定义主题
                    currentTheme = 'custom';
                    GM_setValue('currentTheme', 'custom');
                    document.getElementById('themeSelect').value = 'default'; // 选择默认,但实际上是自定义
                    document.querySelectorAll('.theme-btn').forEach(btn => btn.classList.remove('active'));
                    
                    applyColors();
                });
                
                setTimeout(() => {
                    document.body.removeChild(colorPicker);
                }, 100);
            });
        });
        
        // 应用颜色按钮
        document.getElementById('applyColors').addEventListener('click', () => {
            bgColor = document.getElementById('bgColorInput').value;
            textColor = document.getElementById('textColorInput').value;
            linkColor = document.getElementById('linkColorInput').value;
            
            document.getElementById('bgPreview').style.backgroundColor = bgColor;
            document.getElementById('textPreview').style.backgroundColor = textColor;
            document.getElementById('linkPreview').style.backgroundColor = linkColor;
            
            // 标记为自定义主题
            currentTheme = 'custom';
            GM_setValue('currentTheme', 'custom');
            document.getElementById('themeSelect').value = 'default'; // 选择默认,但实际上是自定义
            document.querySelectorAll('.theme-btn').forEach(btn => btn.classList.remove('active'));
            
            applyColors();
        });
        
        // 重置按钮
        document.getElementById('resetColors').addEventListener('click', resetColors);
        
        // 实时颜色输入更新
        const colorInputs = ['bgColorInput', 'textColorInput', 'linkColorInput'];
        colorInputs.forEach(inputId => {
            const input = document.getElementById(inputId);
            input.addEventListener('input', (e) => {
                const previewId = inputId.replace('Input', 'Preview');
                document.getElementById(previewId).style.backgroundColor = e.target.value;
                
                // 标记为自定义主题
                currentTheme = 'custom';
                GM_setValue('currentTheme', 'custom');
                document.getElementById('themeSelect').value = 'default';
                document.querySelectorAll('.theme-btn').forEach(btn => btn.classList.remove('active'));
            });
            
            input.addEventListener('change', (e) => {
                const previewId = inputId.replace('Input', 'Preview');
                document.getElementById(previewId).style.backgroundColor = e.target.value;
            });
        });
        
        // 按ESC键隐藏面板
        document.addEventListener('keydown', (e) => {
            if (e.key === 'Escape') {
                panel.style.display = 'none';
                toggleBtn.textContent = '🎨';
                panelVisible = false;
            }
        });
    }

    // 等待页面加载完成后初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();

使用方法

安装

  1. 确保浏览器已安装 Tampermonkey 扩展
  2. 在 Tampermonkey 中新建脚本,将上述代码粘贴进去保存
  3. 刷新任意网页,页面右侧会出现一个绿色「颜色」按钮

操作说明

操作说明
点击「颜色」按钮显示/隐藏颜色设置面板
输入十六进制色值直接在输入框填写颜色代码(如 #f5f5f5
点击色块预览弹出系统原生颜色选择器,可视化选色
点击「应用颜色」将当前设置应用到页面并保存
点击「重置默认」恢复白底黑字蓝链接的默认配色

效果

  • 设置即时生效,无需刷新页面
  • 设置会自动保存(通过 GM_setValue),下次访问同一域名自动恢复
  • 点击色块预览可调出系统颜色选择器,支持可视化选色

脚本逻辑详解

整体架构

脚本遵循「面板渲染 → 用户交互 → 样式注入 → 持久化存储」的线性流程:

用户打开网页

脚本执行(@run-at document-end)

读取 GM 存储中的颜色配置

创建 UI 面板和切换按钮

注入自定义 CSS 样式覆盖页面颜色

等待用户操作(修改颜色 / 切换面板 / 重置)

更新存储并重新注入样式

核心模块说明

1. 存储模块(GM API)

利用 Tampermonkey 提供的 GM_getValue / GM_setValue 实现跨会话持久化:

  • GM_getValue('bgColor', '#ffffff') — 读取存储值,不存在则用默认值
  • GM_setValue('bgColor', '#f0f0f0') — 保存新值,下次访问自动恢复

数据按域名隔离,不同网站可以有不同的颜色配置。

2. 样式注入模块

通过动态创建 <style> 标签注入 CSS:

javascript
function applyColors() {
    const css = `
        body, div, section, article, main { 
            background-color: ${bgColor} !important;
            color: ${textColor} !important;
        }
        a, a:link, a:visited { color: ${linkColor} !important; }
    `;
    // 注入到 <head> 中
}

使用 !important 确保覆盖页面原有样式。style 标签带 id="custom-colors-style",多次调用时复用同一标签,避免重复添加。

3. UI 交互模块

  • 颜色面板:固定定位在页面右侧中部,半透明毛玻璃效果
  • 切换按钮:点击显示/隐藏面板,点击时不遮挡页面内容
  • 实时预览:输入框的 input 事件会实时更新色块预览
  • 颜色选择器:点击色块会创建一个隐藏的 <input type="color"> 元素,调起系统原生取色器

4. 选择器覆盖策略

脚本通过 CSS 选择器批量覆盖页面元素的颜色:

目标选择器
主体背景与文字body, div, section, article, main, header, footer, aside, nav
链接颜色a, a:link, a:visited, a:hover, a:active
表单控件input, textarea, select, button(继承背景,用文字颜色)

注意事项

  • 部分网站使用内联样式或 Shadow DOM,脚本的 !important 规则可能无法覆盖
  • @match *://*/* 会匹配所有网页,如果只想在特定网站生效,可修改 @match 规则
  • 颜色值需使用合法的十六进制格式(如 #ffffff),脚本未做格式校验

Move fast and break things