Greasy Fork is available in English.
修改网页背景色,让网页背景色偏白的部分变成乡土黄、豆沙绿等护眼色。优化了性能,解决了动态加载页面的卡顿问题,支持透明背景保留。
// ==UserScript==
// @name 护眼脚本
// @namespace https://zecdn.top
// @description 修改网页背景色,让网页背景色偏白的部分变成乡土黄、豆沙绿等护眼色。优化了性能,解决了动态加载页面的卡顿问题,支持透明背景保留。
// @include http*
// @include ftp*
// @version 1.5
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// ==/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<children.length; i++) processElement(children[i]);
}
});
});
});
}, 100); // 100ms 防抖
});
function init() {
updateGlobalStyle();
// 初次全量扫描(分片处理)
const all = document.querySelectorAll('body, body *');
// 使用 requestIdleCallback 避免阻塞首屏渲染
window.requestIdleCallback(() => {
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 });
}
})();