Greasy Fork is available in English.
优化网页与第三方浏览器插件在 eink 设备上的显示。禁用 transition、animation、box-shadow、filter。加重字体。添加边框。
// ==UserScript==
// @name Eink Polish Beta
// @namespace http://tampermonkey.net/
// @version 3.3
// @description 优化网页与第三方浏览器插件在 eink 设备上的显示。禁用 transition、animation、box-shadow、filter。加重字体。添加边框。
// @author chen
// @match https://*/*
// @exclude https://vscode.dev/*
// @exclude https://gemini.google.com/*
// @exclude https://chatgpt.com/*
// @exclude https://developer.mozilla.org/*
// @run-at document-start
// ==/UserScript==
(function () {
'use strict'; // 开启严格模式,让浏览器更规范地执行代码
// 1. 定义我们要强制生效的 CSS 样式
const hostname = window.location.hostname; // 比如 "www.youtube.com"
const config = {
"*": {
"css": `
div {
/* --- 基础清理:禁用动画和特效 --- */
transition-duration: 0.001s !important; /* 禁用所有渐变过渡, 不可以设置为 none, 小红书图片翻页会出现问题 */
transition-delay: 0s !important;
animation-duration: 0s !important; /* 禁用所有动画, Gemini 推荐不要设置为 none, 会在少数情况出现问题 */
animation-delay: 0s !important;
box-shadow: none !important; /* 禁用所有元素阴影 */
filter: none !important; /* 禁用所有滤镜(比如模糊效果) */
/* --- 控件 --- */
// border-color: #333333 !important; /* 让边框变成深色,防止按钮背景变白后找不到按钮在哪里 */
}
p, h1, h2, h3, h4, h5, h6, li {
font-weight: 600 !important; /* 加粗,提高墨水屏上的辨识度 */
// color: #000 !important; /* 强制纯黑文字 */
// -webkit-text-stroke: 0.5px white; /* 白色描边 */
// paint-order: stroke fill; /* 白色描边不会侵占原本字体 */
}
span, div, a {
font-weight: 600 !important;
}
`,
"shadowCss": `
div {
/* --- 基础清理:禁用动画和特效 --- */
transition: none !important; /* 禁用所有渐变过渡, 不可以设置为 none, 小红书图片翻页会出现问题 */
animation: none !important; /* 禁用所有动画, Gemini 推荐不要设置为 none, 会在少数情况出现问题 */
box-shadow: none !important; /* 禁用所有元素阴影 */
filter: none !important; /* 禁用所有滤镜(比如模糊效果) */
/* --- 控件 --- */
border-color: #333333 !important; /* 让边框变成深色,防止按钮背景变白后找不到按钮在哪里 */
}
`
},
"*.substack.com": {
"css": `
p {
color: #000 !important; /* 强制纯黑文字 */
}
`
},
// "jetbrains.com": {
// "css": `
// p, h1, h2, h3, h4, h5, h6 {
// font-weight: 600 !important; /* 加粗,提高墨水屏上的辨识度 */
// color: #000 !important; /* 强制纯黑文字 */
// -webkit-text-stroke: 1px white; /* 白色描边 */
// paint-order: stroke fill; /* 白色描边不会侵占原本字体 */
// }
// `
// },
// "*.xiaohongshu.com": {
// "css": `
// span {
// color: #000 !important; /* 强制纯黑文字 */
// }
// `
// }
};
/*==== 拼接css ===*/
// 1. 域名匹配与优先级打分函数
function getMatchScore(hostname, ruleKey) {
// 优先级 1:全局匹配 "*"
if (ruleKey === "*") {
return 1;
}
// 优先级最高:精确匹配 (例如 "mail.google.com" 或 "google.com")
// 分数设为 1000,确保它永远在最后被拼接,优先级最高
if (hostname === ruleKey) {
return 1000;
}
// 优先级中等:通配符匹配 (例如 "*.google.com")
if (ruleKey.startsWith("*.")) {
const baseDomain = ruleKey.slice(2); // 取出 "google.com"
// 【重要】只匹配子域名,不匹配主域名(即 *.google.com 不会匹配 google.com)
// 这样实现了 *.google.com 和 google.com 分开处理的要求
if (hostname.endsWith('.' + baseDomain)) {
// 利用 baseDomain 的长度作为分数,实现通配符间的优先级
// 比如 *.mail.google.com (分数 25) 优先级大于 *.google.com (分数 20)
return 10 + baseDomain.length;
}
}
// 0 表示不匹配
return 0;
}
// ==========================================
// 2. 整合调用:收集匹配项 -> 按优先级排序 -> 拼接
// ==========================================
let finalCss = config["*"]?.css || "";
let finalShadowCss = config["*"]?.shadowCss || "";
let matchedRules = []; // 用于存放当前域名匹配到的所有规则
// 遍历所有的配置规则
Object.keys(config).forEach(key => {
const score = getMatchScore(hostname, key);
// 如果 score > 0,说明匹配成功,存入数组
if (score > 0) {
matchedRules.push({
key: key,
score: score,
css: config[key].css,
shadowCss: config[key].shadowCss
});
}
});
// 按优先级升序排序 (分数小的在前面,分数大的在后面覆盖)
matchedRules.sort((a, b) => a.score - b.score);
// 按照排好的顺序拼接 CSS
matchedRules.forEach(rule => {
if (rule.css) {
finalCss += `\n/* From ${rule.key} (Priority: ${rule.score}) */\n${rule.css}`;
}
if (rule.shadowCss) {
finalShadowCss += `\n/* From ${rule.key} (Shadow, Priority: ${rule.score}) */\n${rule.shadowCss}`;
}
});
// 此时 finalCss 和 finalShadowCss 已经完成了拼接,高优先级的在最底下
// 2. 核心动作:把上面的 CSS 注入到指定的“区域”
function injectStyle(target, css) {
// 如果这个区域已经打过我们留下的标记,说明注入过了,直接跳过,防止重复添加
if (target.__styleInjected) return;
// 创建一个 <style> 标签,并把我们的 CSS 文本塞进去
const style = document.createElement('style');
style.textContent = css;
// 把 <style> 标签正式插入到目标区域中
target.appendChild(style);
// 给这个区域打上标记,表示“我已经处理过啦”
target.__styleInjected = true;
}
// 3. 扫描网页节点:寻找并处理那些带有 Shadow DOM 的元素
// 因为你提到“不存在嵌套情况”,所以我们只需要查一次表面即可,不需要无限往下深挖
function scanAndInject(node) {
// 第一步:如果当前检查的这个元素自身就是一个带有 Shadow DOM 的节点,直接注入
if (node.shadowRoot) {
injectStyle(node.shadowRoot, finalShadowCss);
}
// 第二步:如果这个元素是个正常的 HTML 标签(比如 <div>),找找它肚子里有没有带 Shadow DOM 的子元素
if (node.querySelectorAll) {
// querySelectorAll('*') 会把当前节点下的所有子孙元素都找出来
node.querySelectorAll('*').forEach(el => {
if (el.shadowRoot) {
injectStyle(el.shadowRoot, finalShadowCss);
}
});
}
}
// ==========================================
// 下面是脚本的执行主流程
// ==========================================
// 【新增修复】原代码漏掉了最关键的一步:给网页本身(主 DOM)注入样式!
// 我们必须先给主网页注入,这样网页大部分内容的动画和阴影才会被消除
injectStyle(document.head || document.documentElement, finalCss);
// 页面结构刚加载完时,进行一次全局扫描(主要是扫出已经存在的 Shadow DOM)
window.addEventListener('DOMContentLoaded', () => {
scanAndInject(document.body);
});
// 创建一个“监视器” (MutationObserver)
// 作用:现在很多网页(比如 Substack)往下滑动时会动态加载新文章或评论
// 这个监视器就是盯着网页,一旦有新元素加进来,立刻对新元素进行扫描和去动画处理
const observer = new MutationObserver(mutations => {
mutations.forEach(m => {
m.addedNodes.forEach(newNode => {
// nodeType === 1 代表它是一个正常的 HTML 标签元素(排除了普通的纯文本文字)
if (newNode.nodeType === 1) {
scanAndInject(newNode);
}
});
});
});
// 启动监视器,盯着整个网页 (document)
// childList: true 监视子元素的增加或删除
// subtree: true 不仅监视儿子,连孙子、曾孙子(所有后代)的变动也要监视
observer.observe(document, {
childList: true,
subtree: true
});
})();