Greasy Fork

Webpage Background Adjuster

调整网页下方背景的透明度、颜色遮罩、模糊度,并支持上传本地图片以及自定义元素背景调整。通过Tampermonkey菜单调整设置,每个域名独立配置。

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

// ==UserScript==
// @name         Webpage Background Adjuster
// @namespace    http://tampermonkey.net/
// @version      1.47
// @description  调整网页下方背景的透明度、颜色遮罩、模糊度,并支持上传本地图片以及自定义元素背景调整。通过Tampermonkey菜单调整设置,每个域名独立配置。
// @author       Grey333
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// @grant        GM_registerMenuCommand
// ==/UserScript==

(function() {
    'use strict';

    // 默认设置模板
    const defaultSettings = {
        transparency: 1,
        blur: 0,
        overlayColor: 'transparent',
        overlayOpacity: 0,
        customSelector: '',
        customTransparency: 0, // 背景初始不透明
        customBlur: 0
    };

    // 获取当前域名
    const currentDomain = window.location.hostname || 'default';

    // 加载当前域名的设置,若无则使用默认设置
    let settings = JSON.parse(GM_getValue(`settings_${currentDomain}`, JSON.stringify(defaultSettings)));
    let isSelecting = false;

    // 创建样式元素
    const style = document.createElement('style');
    style.id = 'background-adjuster-style';
    document.head.appendChild(style);

    // 设置初始 CSS 变量,反转透明度逻辑
    document.body.style.setProperty('--base-transparency', 1 - settings.transparency);
    document.body.style.setProperty('--base-blur', settings.blur === 0 ? 'none' : `blur(${settings.blur}px)`);
    document.body.style.setProperty('--overlay-color', settings.overlayColor);
    document.body.style.setProperty('--overlay-opacity', settings.overlayOpacity);
    document.body.style.setProperty('--custom-transparency', settings.customTransparency);
    document.body.style.setProperty('--custom-blur', settings.customBlur === 0 ? 'none' : `blur(${settings.customBlur}px)`);

    // 添加默认白色背景层
    const bgLayer = document.createElement('div');
    bgLayer.id = 'background-layer';
    bgLayer.style.cssText = `
        position: fixed;
        top: 0;
        left: 0;
        width: 100vw;
        height: 100vh;
        z-index: -2;
        background: #ffffff;
        opacity: var(--base-transparency);
        filter: var(--base-blur);
    `;
    document.body.insertBefore(bgLayer, document.body.firstChild);

    // 添加颜色遮罩层
    const overlayLayer = document.createElement('div');
    overlayLayer.id = 'overlay-layer';
    overlayLayer.style.cssText = `
        position: fixed;
        top: 0;
        left: 0;
        width: 100vw;
        height: 100vh;
        z-index: -1;
        background: var(--overlay-color);
        opacity: var(--overlay-opacity);
    `;
    document.body.insertBefore(overlayLayer, document.body.firstChild.nextSibling);

    // 添加优化后的 CSS 样式
    style.textContent = `
        body {
            position: relative;
            background: transparent !important;
        }
        #background-layer {
            position: fixed;
            top: 0;
            left: 0;
            width: 100vw;
            height: 100vh;
            z-index: -2;
            background: #ffffff;
            opacity: var(--base-transparency);
            filter: var(--base-blur);
        }
        #overlay-layer {
            position: fixed;
            top: 0;
            left: 0;
            width: 100vw;
            height: 100vh;
            z-index: -1;
            background: var(--overlay-color);
            opacity: var(--overlay-opacity);
        }
        body > *:not(#background-layer):not(#overlay-layer):not(#bg-adjuster-panel) {
            position: relative;
            z-index: 0;
        }
        #bg-adjuster-panel {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: rgba(255, 255, 255, 0.95);
            border: none;
            padding: 20px;
            border-radius: 15px;
            box-shadow: 0 6px 20px rgba(0,0,0,0.15);
            z-index: 10000;
            display: none;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
            width: 280px;
            max-height: 80vh;
            overflow-y: auto;
        }
        #bg-adjuster-panel label {
            display: flex;
            align-items: center;
            margin: 15px 0;
            font-size: 14px;
            color: #333;
        }
        #bg-adjuster-panel input[type="range"] {
            flex: 1;
            margin: 0 10px;
            height: 10px;
            border-radius: 5px;
            background: #e0e0e0;
            position: relative;
            outline: none;
            cursor: pointer;
            appearance: none;
        }
        #bg-adjuster-panel input[type="range"]::-webkit-slider-runnable-track {
            height: 10px;
            border-radius: 5px;
        }
        #bg-adjuster-panel input[type="range"]::-moz-range-track {
            height: 10px;
            border-radius: 5px;
        }
        #bg-adjuster-panel input[type="range"]::-webkit-slider-thumb {
            appearance: none;
            width: 18px;
            height: 18px;
            background: linear-gradient(135deg, #4a90e2, #357abd);
            border-radius: 50%;
            border: 2px solid #fff;
            cursor: pointer;
            box-shadow: 0 2px 6px rgba(0,0,0,0.2);
            margin-top: -4px;
        }
        #bg-adjuster-panel input[type="range"]::-moz-range-thumb {
            width: 18px;
            height: 18px;
            background: linear-gradient(135deg, #4a90e2, #357abd);
            border-radius: 50%;
            border: 2px solid #fff;
            cursor: pointer;
            box-shadow: 0 2px 6px rgba(0,0,0,0.2);
        }
        #bg-adjuster-panel .percentage {
            width: 40px;
            text-align: right;
            font-size: 12px;
            color: #666;
        }
        #bg-adjuster-panel .color-btn {
            width: 24px;
            height: 24px;
            border-radius: 50%;
            border: 2px solid #ddd;
            cursor: pointer;
            margin-right: 8px;
            transition: transform 0.2s, box-shadow 0.2s;
        }
        #bg-adjuster-panel .color-btn:hover {
            transform: scale(1.2);
            box-shadow: 0 0 4px rgba(0,0,0,0.3);
        }
        #bg-adjuster-panel .color-btn:active {
            transform: scale(1.1);
            box-shadow: 0 0 6px rgba(0,0,0,0.4);
        }
        #bg-adjuster-panel #none-btn {
            background: transparent;
            border: 2px dashed #ccc;
        }
        #bg-adjuster-panel #yellow-btn {
            background: #ffff00;
        }
        #bg-adjuster-panel #green-btn {
            background: #00ff00;
        }
        #bg-adjuster-panel #custom-color {
            width: 40px;
            height: 40px;
            padding: 0;
            border: 2px solid #ddd;
            border-radius: 8px;
            cursor: pointer;
            margin-left: auto;
        }
        #bg-adjuster-panel button {
            padding: 6px 12px;
            cursor: pointer;
            border-radius: 8px;
            border: none;
            background: #f0f0f0;
            font-size: 12px;
            transition: background 0.2s;
        }
        #bg-adjuster-panel button:hover {
            background: #e0e0e0;
        }
        #bg-adjuster-panel #reset-btn {
            background: #ff3b30;
            color: white;
        }
        #bg-adjuster-panel #reset-btn:hover {
            background: #e6392e;
        }
        #bg-adjuster-panel #close-btn {
            background: #4a90e2;
            color: white;
        }
        #bg-adjuster-panel #close-btn:hover {
            background: #357abd;
        }
        #bg-image-label {
            display: block;
            margin: 15px 0;
            font-size: 14px;
            color: #333;
            white-space: nowrap;
        }
        #bg-image-label input[type="file"] {
            width: 100%;
            font-size: 12px;
            margin-top: 5px;
        }
        #overlay-opacity-section {
            display: none;
        }
        #custom-section {
            display: none;
            margin-top: 15px;
        }
        #custom-input {
            width: 100%;
            padding: 5px;
            font-size: 12px;
            border: 1px solid #ddd;
            border-radius: 4px;
            margin-bottom: 10px;
        }
        #custom-btn {
            background: #6a4ae2;
            color: white;
        }
        #custom-btn:hover {
            background: #5a3abd;
        }
    `;

    // 创建 UI 面板
    const panel = document.createElement('div');
    panel.id = 'bg-adjuster-panel';
    document.body.appendChild(panel);

    // UI 面板内容
    panel.innerHTML = `
        <h3 style="margin: 0 0 20px 0; font-size: 16px; text-align: center; color: #333;">背景调节器 (${currentDomain})</h3>
        <label>透明度: <input type="range" min="0" max="1" step="0.01" id="transparency-slider" value="${settings.transparency}"><span class="percentage">${Math.round(settings.transparency * 100)}%</span></label>
        <label>模糊度: <input type="range" min="0" max="20" step="1" id="blur-slider" value="${settings.blur}"><span class="percentage">${Math.round((settings.blur / 20) * 100)}%</span></label>
        <div id="color-overlay-section">
            <label style="display: flex; align-items: center;">颜色遮罩:
                <button class="color-btn" id="none-btn" title="无"></button>
                <button class="color-btn" id="yellow-btn" title="黄色"></button>
                <button class="color-btn" id="green-btn" title="绿色"></button>
                <input type="color" id="custom-color" value="${settings.overlayColor === 'transparent' ? '#ffffff' : settings.overlayColor}" title="自定义颜色">
            </label>
        </div>
        <div id="overlay-opacity-section">
            <label id="overlay-opacity-label">遮罩透明度: <input type="range" min="0" max="1" step="0.01" id="overlay-opacity" value="${settings.overlayOpacity}"><span class="percentage">${Math.round(settings.overlayOpacity * 100)}%</span></label>
        </div>
        <label id="bg-image-label">背景图片:<input type="file" id="bg-image-input" accept="image/*"></label>
        <button id="custom-btn" style="width: 100%; margin-top: 15px;">自定义</button>
        <div id="custom-section">
            <input type="text" id="custom-input" value="${settings.customSelector}" placeholder="输入或选择CSS选择器">
            <label>自定义透明度: <input type="range" min="0" max="1" step="0.01" id="custom-transparency-slider" value="${settings.customTransparency}"><span class="percentage">${Math.round(settings.customTransparency * 100)}%</span></label>
            <label>自定义模糊度: <input type="range" min="0" max="20" step="1" id="custom-blur-slider" value="${settings.customBlur}"><span class="percentage">${Math.round((settings.customBlur / 20) * 100)}%</span></label>
        </div>
        <button id="reset-btn" style="width: 100%; margin-top: 15px;">重置</button>
        <button id="close-btn" style="width: 100%; margin-top: 10px;">关闭</button>
    `;

    // 获取 UI 元素
    const transparencySlider = document.getElementById('transparency-slider');
    const blurSlider = document.getElementById('blur-slider');
    const noneBtn = document.getElementById('none-btn');
    const yellowBtn = document.getElementById('yellow-btn');
    const greenBtn = document.getElementById('green-btn');
    const customColor = document.getElementById('custom-color');
    const overlayOpacity = document.getElementById('overlay-opacity');
    const overlayOpacityLabel = document.getElementById('overlay-opacity-label');
    const overlayOpacitySection = document.getElementById('overlay-opacity-section');
    const bgImageInput = document.getElementById('bg-image-input');
    const customBtn = document.getElementById('custom-btn');
    const customSection = document.getElementById('custom-section');
    const customInput = document.getElementById('custom-input');
    const customTransparencySlider = document.getElementById('custom-transparency-slider');
    const customBlurSlider = document.getElementById('custom-blur-slider');
    const resetBtn = document.getElementById('reset-btn');
    const closeBtn = document.getElementById('close-btn');
    const backgroundLayer = document.getElementById('background-layer');
    const overlayLayerElement = document.getElementById('overlay-layer');
    const transparencyPercentage = transparencySlider.nextElementSibling;
    const blurPercentage = blurSlider.nextElementSibling;
    const overlayOpacityPercentage = overlayOpacity.nextElementSibling;
    const customTransparencyPercentage = customTransparencySlider.nextElementSibling;
    const customBlurPercentage = customBlurSlider.nextElementSibling;

    // 更新设置和 CSS 变量的函数
    function updateSettings() {
        settings.transparency = parseFloat(transparencySlider.value);
        settings.blur = parseInt(blurSlider.value);
        settings.overlayColor = customColor.value === '#ffffff' && overlayOpacity.value == 0 ? 'transparent' : customColor.value;
        settings.overlayOpacity = parseFloat(overlayOpacity.value);
        settings.customSelector = customInput.value;
        settings.customTransparency = parseFloat(customTransparencySlider.value);
        settings.customBlur = parseInt(customBlurSlider.value);

        document.body.style.setProperty('--base-transparency', 1 - settings.transparency);
        document.body.style.setProperty('--base-blur', settings.blur === 0 ? 'none' : `blur(${settings.blur}px)`);
        document.body.style.setProperty('--overlay-color', settings.overlayColor);
        document.body.style.setProperty('--overlay-opacity', settings.overlayOpacity);
        document.body.style.setProperty('--custom-transparency', settings.customTransparency);
        document.body.style.setProperty('--custom-blur', settings.customBlur === 0 ? 'none' : `blur(${settings.customBlur}px)`);

        // 按域名保存设置
        GM_setValue(`settings_${currentDomain}`, JSON.stringify(settings));

        transparencyPercentage.textContent = `${Math.round(settings.transparency * 100)}%`;
        blurPercentage.textContent = `${Math.round((settings.blur / 20) * 100)}%`;
        overlayOpacityPercentage.textContent = `${Math.round(settings.overlayOpacity * 100)}%`;
        customTransparencyPercentage.textContent = `${Math.round(settings.customTransparency * 100)}%`;
        customBlurPercentage.textContent = `${Math.round((settings.customBlur / 20) * 100)}%`;

        transparencySlider.style.background = `linear-gradient(to right, #4a90e2 ${settings.transparency * 100}%, #e0e0e0 ${settings.transparency * 100}%)`;
        blurSlider.style.background = `linear-gradient(to right, #4a90e2 ${(settings.blur / 20) * 100}%, #e0e0e0 ${(settings.blur / 20) * 100}%)`;
        overlayOpacity.style.background = `linear-gradient(to right, #4a90e2 ${settings.overlayOpacity * 100}%, #e0e0e0 ${settings.overlayOpacity * 100}%)`;
        customTransparencySlider.style.background = `linear-gradient(to right, #4a90e2 ${settings.customTransparency * 100}%, #e0e0e0 ${settings.customTransparency * 100}%)`;
        customBlurSlider.style.background = `linear-gradient(to right, #4a90e2 ${(settings.customBlur / 20) * 100}%, #e0e0e0 ${(settings.customBlur / 20) * 100}%)`;

        // 显示或隐藏遮罩透明度滑块
        overlayOpacitySection.style.display = settings.overlayColor === 'transparent' ? 'none' : 'block';

        // 应用自定义选择器的背景样式
        if (settings.customSelector) {
            try {
                const elements = document.querySelectorAll(settings.customSelector);
                console.log(`Matched elements for "${settings.customSelector}":`, elements.length);
                elements.forEach((el, index) => {
                    console.log(`Element ${index}:`, el);

                    // 获取原始背景
                    let originalBg = window.getComputedStyle(el).background;
                    if (!originalBg || originalBg === 'rgba(0, 0, 0, 0)' || originalBg === 'transparent') {
                        originalBg = window.getComputedStyle(el).backgroundColor;
                    }
                    if (!originalBg || originalBg === 'rgba(0, 0, 0, 0)') {
                        console.log(`Element ${index} has no direct background, checking parent...`);
                        let parent = el.parentElement;
                        while (parent && (!originalBg || originalBg === 'rgba(0, 0, 0, 0)' || originalBg === 'transparent')) {
                            originalBg = window.getComputedStyle(parent).backgroundColor;
                            parent = parent.parentElement;
                        }
                        if (!originalBg || originalBg === 'rgba(0, 0, 0, 0)') {
                            originalBg = 'rgba(255, 255, 255, 1)'; // 默认白色
                        }
                    }
                    console.log(`Original background for element ${index}:`, originalBg);

                    // 处理背景颜色转换为 rgba
                    let rgbaMatch = originalBg.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/i);
                    let bgStyle = originalBg;
                    if (rgbaMatch) {
                        const r = rgbaMatch[1];
                        const g = rgbaMatch[2];
                        const b = rgbaMatch[3];
                        const a = settings.customTransparency;
                        bgStyle = `rgba(${r}, ${g}, ${b}, ${1 - a})`;
                    } else if (originalBg.includes('url')) {
                        bgStyle = `${originalBg}, rgba(0, 0, 0, ${1 - settings.customTransparency})`;
                    } else {
                        bgStyle = `rgba(255, 255, 255, ${1 - settings.customTransparency})`;
                    }

                    // 应用背景样式
                    el.style.background = bgStyle;
                    el.style.backdropFilter = settings.customBlur === 0 ? 'none' : `blur(${settings.customBlur}px)`;
                    el.style.webkitBackdropFilter = settings.customBlur === 0 ? 'none' : `blur(${settings.customBlur}px)`;

                    // 记录原始样式
                    if (!el.dataset.originalBackground) {
                        el.dataset.originalBackground = originalBg;
                    }
                    if (!el.dataset.originalBackdropFilter) {
                        el.dataset.originalBackdropFilter = window.getComputedStyle(el).backdropFilter || 'none';
                    }
                });
            } catch (e) {
                console.error('Invalid CSS selector:', settings.customSelector, e);
            }
        }
    }

    // 为进度条添加事件监听
    transparencySlider.addEventListener('input', updateSettings);
    blurSlider.addEventListener('input', updateSettings);
    overlayOpacity.addEventListener('input', updateSettings);
    customColor.addEventListener('change', updateSettings);
    customTransparencySlider.addEventListener('input', updateSettings);
    customBlurSlider.addEventListener('input', updateSettings);
    customInput.addEventListener('change', updateSettings);

    // 为颜色按钮添加事件监听
    noneBtn.addEventListener('click', () => {
        customColor.value = '#ffffff';
        overlayOpacity.value = 0;
        updateSettings();
    });
    yellowBtn.addEventListener('click', () => {
        customColor.value = '#ffff00';
        if (settings.overlayOpacity === 0) overlayOpacity.value = 0.5;
        updateSettings();
    });
    greenBtn.addEventListener('click', () => {
        customColor.value = '#00ff00';
        if (settings.overlayOpacity === 0) overlayOpacity.value = 0.5;
        updateSettings();
    });

    // 处理背景图片上传
    bgImageInput.addEventListener('change', function() {
        const file = this.files[0];
        if (file) {
            const reader = new FileReader();
            reader.onload = function(e) {
                backgroundLayer.style.backgroundImage = `url(${e.target.result})`;
                backgroundLayer.style.backgroundSize = 'cover';
                backgroundLayer.style.backgroundRepeat = 'no-repeat';
                backgroundLayer.style.backgroundColor = 'transparent';
                settings.backgroundImage = e.target.result; // 保存图片 URL
                updateSettings();
            };
            reader.readAsDataURL(file);
        }
    });

    // 自定义按钮功能
    customBtn.addEventListener('click', () => {
        customSection.style.display = customSection.style.display === 'block' ? 'none' : 'block';
        if (customSection.style.display === 'block') {
            isSelecting = true;
            document.body.style.cursor = 'crosshair';
            document.addEventListener('click', handleElementSelection);
        } else {
            isSelecting = false;
            document.body.style.cursor = 'default';
            document.removeEventListener('click', handleElementSelection);
        }
    });

    // 处理元素选择
    function handleElementSelection(e) {
        e.preventDefault();
        e.stopPropagation();
        const target = e.target;
        if (panel.contains(target)) return;
        const selector = generateSelector(target);
        customInput.value = selector;
        settings.customSelector = selector;
        updateSettings();
        isSelecting = false;
        document.body.style.cursor = 'default';
        document.removeEventListener('click', handleElementSelection);
    }

    // 生成 CSS 选择器
    function generateSelector(element) {
        if (element.id) return `#${element.id}`;
        if (element === document.body) return 'body';
        let path = [];
        let current = element;
        while (current && current !== document.body) {
            let selector = current.tagName.toLowerCase();
            if (current.className) {
                selector += '.' + current.className.trim().split(/\s+/).join('.');
            }
            path.unshift(selector);
            current = current.parentElement;
        }
        return path.join(' > ');
    }

    // 重置按钮功能
    resetBtn.addEventListener('click', () => {
        settings = Object.assign({}, defaultSettings);
        transparencySlider.value = settings.transparency;
        blurSlider.value = settings.blur;
        customColor.value = '#ffffff';
        overlayOpacity.value = 0;
        customInput.value = '';
        customTransparencySlider.value = settings.customTransparency;
        customBlurSlider.value = settings.customBlur;
        document.body.style.setProperty('--base-transparency', 1 - settings.transparency);
        document.body.style.setProperty('--base-blur', 'none');
        document.body.style.setProperty('--overlay-color', 'transparent');
        document.body.style.setProperty('--overlay-opacity', 0);
        document.body.style.setProperty('--custom-transparency', settings.customTransparency);
        document.body.style.setProperty('--custom-blur', 'none');
        backgroundLayer.style.backgroundImage = 'none';
        backgroundLayer.style.backgroundColor = '#ffffff';
        customSection.style.display = 'none';
        isSelecting = false;
        document.body.style.cursor = 'default';
        document.removeEventListener('click', handleElementSelection);

        // 恢复元素的原始背景样式
        if (settings.customSelector) {
            try {
                const elements = document.querySelectorAll(settings.customSelector);
                elements.forEach(el => {
                    el.style.background = el.dataset.originalBackground || '';
                    el.style.backdropFilter = el.dataset.originalBackdropFilter || 'none';
                    el.style.webkitBackdropFilter = el.dataset.originalBackdropFilter || 'none';
                    delete el.dataset.originalBackground;
                    delete el.dataset.originalBackdropFilter;
                });
            } catch (e) {
                console.error('Error resetting elements:', e);
            }
        }

        GM_setValue(`settings_${currentDomain}`, JSON.stringify(settings));
        transparencyPercentage.textContent = `${Math.round(settings.transparency * 100)}%`;
        blurPercentage.textContent = `${Math.round((settings.blur / 20) * 100)}%`;
        overlayOpacityPercentage.textContent = `${Math.round(settings.overlayOpacity * 100)}%`;
        customTransparencyPercentage.textContent = `${Math.round(settings.customTransparency * 100)}%`;
        customBlurPercentage.textContent = `${Math.round((settings.customBlur / 20) * 100)}%`;
        updateSettings();
    });

    // 关闭面板
    closeBtn.addEventListener('click', () => {
        panel.style.display = 'none';
        document.body.style.cursor = 'default';
        document.removeEventListener('click', handleElementSelection);
        customSection.style.display = 'none';
        isSelecting = false;
    });

    // 点击面板外关闭(在非选择模式下)
    document.addEventListener('click', (e) => {
        if (!isSelecting && !panel.contains(e.target) && panel.style.display === 'block') {
            panel.style.display = 'none';
            document.body.style.cursor = 'default';
            document.removeEventListener('click', handleElementSelection);
            customSection.style.display = 'none';
        }
    });

    // 防止面板内部点击冒泡到外部关闭
    panel.addEventListener('click', (e) => {
        e.stopPropagation();
    });

    // Tampermonkey 菜单选项
    GM_registerMenuCommand('打开背景调节器', () => {
        panel.style.display = 'block';
    });

    // 初始化设置
    updateSettings();

    // 如果有保存的背景图片,应用它
    if (settings.backgroundImage) {
        backgroundLayer.style.backgroundImage = `url(${settings.backgroundImage})`;
        backgroundLayer.style.backgroundSize = 'cover';
        backgroundLayer.style.backgroundRepeat = 'no-repeat';
        backgroundLayer.style.backgroundColor = 'transparent';
    }
})();