// ==UserScript== // @name 护眼脚本 // @namespace https://zecdn.top // @description 修改网页背景色,让网页背景色偏白的部分变成乡土黄、豆沙绿等护眼色。优化了性能,解决了动态加载页面的卡顿问题,支持透明背景保留。 // @icon http://d.lanrentuku.com/down/png/1406/40xiaodongwu/tropical-fish.png // @include http* // @include ftp* // @version 1.3 // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @downloadURL none // ==/UserScript== (function() { 'use strict'; const CONFIG = { threshold: 242, colors: { yellow: { name: "乡土黄", hex: "#F6F4EC" }, green: { name: "豆沙绿", hex: "#CCE8CF" }, grey: { name: "浅色灰", hex: "#F2F2F2" }, olive: { name: "淡橄榄", hex: "#E1E6D7" } }, defaultColorKey: "yellow" }; let selectedKey = GM_getValue("colorValue", CONFIG.defaultColorKey); let currentColorHex = CONFIG.colors[selectedKey].hex; // --- 核心优化 1: 使用 CSS 注入代替逐个修改 style 属性 --- // 这样不需要在 MutationObserver 里频繁操作 DOM 属性,减少重排 const styleId = 'eye-protection-style'; function updateGlobalStyle() { let styleTag = document.getElementById(styleId); if (!styleTag) { styleTag = document.createElement('style'); styleTag.id = styleId; document.documentElement.appendChild(styleTag); } // 这里的逻辑:只针对标记了 data-eye-protected 的元素生效 styleTag.innerHTML = `[data-eye-protected="true"] { background-color: ${currentColorHex} !important; }`; } function isLightColor(colorStr) { if (!colorStr || colorStr === 'transparent' || colorStr.includes('rgba(0, 0, 0, 0)')) return false; const rgb = colorStr.match(/\d+/g); if (!rgb || rgb.length < 3) return false; // 只要 R, G, B 都大于阈值 return rgb[0] > CONFIG.threshold && rgb[1] > CONFIG.threshold && rgb[2] > CONFIG.threshold; } // --- 核心优化 2: 精简处理逻辑 --- function processElement(el) { if (el.nodeType !== 1) return; // 已经处理过的跳过 if (el.dataset.eyeProtected) return; const bg = window.getComputedStyle(el).backgroundColor; if (isLightColor(bg)) { el.setAttribute('data-eye-protected', 'true'); } } // --- 核心优化 3: 带有防抖和任务分解的观察器 --- let timer = null; const observer = new MutationObserver((mutations) => { if (timer) clearTimeout(timer); timer = setTimeout(() => { // 每次只处理新增的顶层节点,不进行深度递归扫描 // 深度扫描交给 requestIdleCallback 在浏览器空闲时做 requestIdleCallback(() => { mutations.forEach(m => { m.addedNodes.forEach(node => { if (node.nodeType === 1) { processElement(node); // 只扫描一层子节点,避免知乎这种深层 DOM 导致的卡死 const children = node.children; for(let i=0; i { all.forEach(el => processElement(el)); }); observer.observe(document.body, { childList: true, subtree: true }); } // 注册菜单 for (const [key, val] of Object.entries(CONFIG.colors)) { GM_registerMenuCommand(val.name, () => { GM_setValue("colorValue", key); location.reload(); // 切换颜色建议刷新,性能最稳 }); } // 确保 body 存在后再运行 if (document.body) { init(); } else { const observerBody = new MutationObserver(() => { if (document.body) { observerBody.disconnect(); init(); } }); observerBody.observe(document.documentElement, { childList: true }); } })();