护眼模式

简单有效的全网通用护眼模式(夜间模式、暗黑模式、深色模式)

当前为 2021-09-30 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name 护眼模式
  3. // @version 1.2.9
  4. // @author X.I.U
  5. // @description 简单有效的全网通用护眼模式(夜间模式、暗黑模式、深色模式)
  6. // @match *://*/*
  7. // @exclude *://*.iqiyi.com/*
  8. // @exclude *://*.youku.com/*
  9. // @icon https://i.loli.net/2021/03/07/rdijeYm83pznxWq.png
  10. // @grant GM_registerMenuCommand
  11. // @grant GM_unregisterMenuCommand
  12. // @grant GM_openInTab
  13. // @grant GM_getValue
  14. // @grant GM_setValue
  15. // @grant GM_notification
  16. // @noframes
  17. // @license GPL-3.0 License
  18. // @run-at document-start
  19. // @namespace https://github.com/XIU2/UserScript
  20. // @supportURL https://github.com/XIU2/UserScript
  21. // @homepageURL https://github.com/XIU2/UserScript
  22. // ==/UserScript==
  23.  
  24. (function() {
  25. 'use strict';
  26. var menu_ALL = [
  27. ['menu_disable', '✅ 已启用 (点击对当前网站禁用)', '❌ 已禁用 (点击对当前网站启用)', []],
  28. ['menu_runDuringTheDay', '白天保持开启 (比晚上亮一点点)', '白天保持开启', true],
  29. ['menu_darkModeAuto', '护眼模式跟随浏览器', '护眼模式跟随浏览器', false],
  30. ['menu_autoRecognition', '智能排除自带暗黑模式的网页 (beta)', '智能排除自带暗黑模式的网页 (beta)', true],
  31. ['menu_forcedToEnable', '✅ 已强制当前网站启用护眼模式 (👆)', '❌ 未强制当前网站启用护眼模式 (👆)', []],
  32. ['menu_darkModeType', '点击切换模式', '点击切换模式', 2],
  33. ['menu_customMode', '自定义当前模式', '自定义当前模式', true], ['menu_customMode1',,,'80|70'], ['menu_customMode2',,,'80|20|70|30'], ['menu_customMode3',,,'80'],
  34. ['menu_autoSwitch', '晚上自动切换模式', '晚上自动切换模式', ''],
  35. ], menu_ID = [];
  36. for (let i=0;i<menu_ALL.length;i++){ // 如果读取到的值为 null 就写入默认值
  37. if (GM_getValue(menu_ALL[i][0]) == null){GM_setValue(menu_ALL[i][0], menu_ALL[i][3])};
  38. }
  39. registerMenuCommand();
  40. if (menu_ID.length > 1) {addStyle();}
  41.  
  42.  
  43. // 注册脚本菜单
  44. function registerMenuCommand() {
  45. if (menu_ID.length != []){
  46. for (let i=0;i<menu_ID.length;i++){
  47. GM_unregisterMenuCommand(menu_ID[i]);
  48. }
  49. }
  50. for (let i=0;i<menu_ALL.length;i++){ // 循环注册脚本菜单
  51. menu_ALL[i][3] = GM_getValue(menu_ALL[i][0]);
  52. if (menu_ALL[i][0] === 'menu_disable')
  53. { // 启用/禁用护眼模式 (当前网站)
  54. if (menu_disable('check')) { // 当前网站是否已存在禁用列表中
  55. menu_ID[i] = GM_registerMenuCommand(`${menu_ALL[i][2]}`, function(){menu_disable('del')});
  56. return
  57. } else {
  58. if (GM_getValue('menu_darkModeAuto') && !window.matchMedia('(prefers-color-scheme: dark)').matches) {
  59. menu_ID[i] = GM_registerMenuCommand(`❌ 当前浏览器为白天模式 (点击关闭 [护眼模式跟随浏览器])`, function(){GM_setValue('menu_darkModeAuto', false);location.reload();});
  60. return
  61. }
  62. menu_ID[i] = GM_registerMenuCommand(`${menu_ALL[i][1]}`, function(){menu_disable('add')});
  63. }
  64. }
  65. else if (menu_ALL[i][0] === 'menu_darkModeType')
  66. { // 点击切换模式
  67. if (menu_ALL[i][3] > 3) { // 避免在减少 raw 数组后,用户储存的数据大于数组而报错
  68. menu_ALL[i][3] = 1;
  69. GM_setValue(menu_ALL[i][0], menu_ALL[i][3]);
  70. }
  71. let menu_newMode = getAutoSwitch();
  72. menu_ID[i] = GM_registerMenuCommand(`${menu_num(menu_newMode)} ${menu_ALL[i][1]}`, function(){menu_toggle(`${menu_ALL[i][3]}`,`${menu_ALL[i][0]}`)});
  73. }
  74. else if (menu_ALL[i][0] === 'menu_customMode')
  75. { // 自定义当前模式
  76. GM_setValue(menu_ALL[i][0], menu_ALL[i][3]);
  77. menu_ID[i] = GM_registerMenuCommand(`#️⃣ ${menu_ALL[i][1]}`, function(){menu_customMode()});
  78. }
  79. else if (menu_ALL[i][0] === 'menu_customMode1' || menu_ALL[i][0] === 'menu_customMode2' || menu_ALL[i][0] === 'menu_customMode3')
  80. { // 当前模式值
  81. GM_setValue(menu_ALL[i][0], menu_ALL[i][3]);
  82. }
  83. else if (menu_ALL[i][0] === 'menu_autoSwitch')
  84. { // 晚上自动切换模式
  85. menu_ID[i] = GM_registerMenuCommand(`#️⃣ ${menu_ALL[i][1]}`, function(){menu_customAutoSwitch()});
  86. }
  87. else if (menu_ALL[i][0] === 'menu_forcedToEnable')
  88. { // 强制当前网站启用护眼模式
  89. if (menu_value('menu_autoRecognition')) { // 自动排除自带暗黑模式的网页 (beta)
  90. if (menu_forcedToEnable('check')) { // 当前网站是否已存在列表中
  91. menu_ID[i] = GM_registerMenuCommand(`${menu_ALL[i][1]}`, function(){menu_forcedToEnable('del')});
  92. } else {
  93. menu_ID[i] = GM_registerMenuCommand(`${menu_ALL[i][2]}`, function(){menu_forcedToEnable('add')});
  94. }
  95. }
  96. }
  97. else
  98. {
  99. menu_ID[i] = GM_registerMenuCommand(`${menu_ALL[i][3]?'✅':'❌'} ${menu_ALL[i][1]}`, function(){menu_switch(`${menu_ALL[i][3]}`,`${menu_ALL[i][0]}`,`${menu_ALL[i][2]}`)});
  100. }
  101. }
  102. menu_ID[menu_ID.length] = GM_registerMenuCommand('💬 反馈 & 建议', function () {window.GM_openInTab('https://github.com/XIU2/UserScript#xiu2userscript', {active: true,insert: true,setParent: true});window.GM_openInTab('https://greasyfork.org/zh-CN/scripts/412212/feedback', {active: true,insert: true,setParent: true});});
  103. }
  104.  
  105.  
  106. // 菜单数字图标
  107. function menu_num(num) {
  108. return ['0️⃣','1️⃣','2️⃣','3️⃣','4️⃣','5️⃣','6️⃣','7️⃣','8️⃣','9️⃣','🔟'][num]
  109. }
  110.  
  111.  
  112. // 晚上自动切换模式
  113. function menu_customAutoSwitch() {
  114. let newAutoSwitch = prompt('白天、晚上使用不同模式,修改后立即生效~\n格式:白天模式|晚上模式\n例如:1|3(即白天模式 1 晚上模式 2)\n默认:留空(即关闭该功能)', GM_getValue('menu_autoSwitch'));
  115. if (newAutoSwitch === '') {
  116. GM_setValue('menu_autoSwitch', '');
  117. } else if (newAutoSwitch != null) {
  118. if (newAutoSwitch.split('|').length == 2) {
  119. GM_setValue('menu_autoSwitch', newAutoSwitch);
  120. } else {
  121. alert(`填入内容格式错误...`);
  122. }
  123. }
  124. registerMenuCommand(); // 重新注册脚本菜单
  125. if (document.getElementById('XIU2DarkMode')) {
  126. document.getElementById('XIU2DarkMode').remove(); // 即时修改样式
  127. addStyle();
  128. }
  129. }
  130. // 获取当前模式
  131. function getAutoSwitch() {
  132. let darkModeType = GM_getValue('menu_darkModeType'), hours = new Date().getHours();
  133. if (GM_getValue('menu_autoSwitch') != '') { // 晚上自动切换模式
  134. if (hours > 6 && hours < 19) { // 白天
  135. darkModeType = GM_getValue('menu_autoSwitch').split('|')[0];
  136. } else { // 晚上
  137. darkModeType = GM_getValue('menu_autoSwitch').split('|')[1];
  138. }
  139. }
  140. return parseInt(darkModeType)
  141. }
  142.  
  143.  
  144. // 自定义当前模式
  145. function menu_customMode() {
  146. let newMods, tip, defaults, name;
  147. switch(getAutoSwitch()) {
  148. case 1:
  149. tip = '自定义 [模式 1],修改后立即生效 (部分网页可能需要刷新)~\n格式:亮度 (白天)|亮度 (晚上)\n默认:80|70(均为百分比 1~100,不需要 % 符号)';
  150. defaults = '80|70';
  151. name = 'menu_customMode1';
  152. break;
  153. case 2:
  154. tip = '自定义 [模式 2],修改后立即生效 (部分网页可能需要刷新)~\n格式:亮度 (白天)|暖色 (白天)|亮度 (晚上)|暖色 (晚上)\n默认:80|20|70|30(均为百分比 1~100,不需要 % 符号)';
  155. defaults = '80|20|70|30';
  156. name = 'menu_customMode2';
  157. break;
  158. case 3:
  159. tip = '自定义 [模式 3],修改后立即生效 (部分网页可能需要刷新)~\n格式:反色\n默认:80(均为百分比 50~100,不需要 % 符号)';
  160. defaults = '80';
  161. name = 'menu_customMode3';
  162. break;
  163. }
  164. newMods = prompt(tip, GM_getValue(`${name}`));
  165. if (newMods === '') {
  166. GM_setValue(`${name}`, defaults);
  167. registerMenuCommand(); // 重新注册脚本菜单
  168. } else if (newMods != null) {
  169. GM_setValue(`${name}`, newMods);
  170. registerMenuCommand(); // 重新注册脚本菜单
  171. }
  172. if (document.getElementById('XIU2DarkMode')) {
  173. document.getElementById('XIU2DarkMode').remove(); // 即时修改样式
  174. addStyle();
  175. }
  176. }
  177.  
  178.  
  179. // 强制当前网站启用护眼模式
  180. function menu_forcedToEnable(type) {
  181. switch(type) {
  182. case 'check':
  183. if(check()) return true
  184. return false
  185. break;
  186. case 'add':
  187. add();
  188. break;
  189. case 'del':
  190. del();
  191. break;
  192. }
  193.  
  194. function check() { // 存在返回真,不存在返回假
  195. let websiteList = menu_value('menu_forcedToEnable'); // 读取网站列表
  196. if (websiteList.indexOf(location.host) === -1) return false // 不存在返回假
  197. return true
  198. }
  199.  
  200. function add() {
  201. if (check()) return
  202. let websiteList = menu_value('menu_forcedToEnable'); // 读取网站列表
  203. websiteList.push(location.host); // 追加网站域名
  204. GM_setValue('menu_forcedToEnable', websiteList); // 写入配置
  205. location.reload(); // 刷新网页
  206. }
  207.  
  208. function del() {
  209. if (!check()) return
  210. let websiteList = menu_value('menu_forcedToEnable'), // 读取网站列表
  211. index = websiteList.indexOf(location.host);
  212. websiteList.splice(index, 1); // 删除网站域名
  213. GM_setValue('menu_forcedToEnable', websiteList); // 写入配置
  214. location.reload(); // 刷新网页
  215. }
  216. }
  217.  
  218.  
  219. // 启用/禁用护眼模式 (当前网站)
  220. function menu_disable(type) {
  221. switch(type) {
  222. case 'check':
  223. if(check()) return true
  224. return false
  225. break;
  226. case 'add':
  227. add();
  228. break;
  229. case 'del':
  230. del();
  231. break;
  232. }
  233.  
  234. function check() { // 存在返回真,不存在返回假
  235. let websiteList = menu_value('menu_disable'); // 读取网站列表
  236. if (websiteList.indexOf(location.host) === -1) return false // 不存在返回假
  237. return true
  238. }
  239.  
  240. function add() {
  241. if (check()) return
  242. let websiteList = menu_value('menu_disable'); // 读取网站列表
  243. websiteList.push(location.host); // 追加网站域名
  244. GM_setValue('menu_disable', websiteList); // 写入配置
  245. location.reload(); // 刷新网页
  246. }
  247.  
  248. function del() {
  249. if (!check()) return
  250. let websiteList = menu_value('menu_disable'), // 读取网站列表
  251. index = websiteList.indexOf(location.host);
  252. websiteList.splice(index, 1); // 删除网站域名
  253. GM_setValue('menu_disable', websiteList); // 写入配置
  254. location.reload(); // 刷新网页
  255. }
  256. }
  257.  
  258.  
  259. // 切换暗黑模式
  260. function menu_toggle(menu_status, Name) {
  261. menu_status = parseInt(menu_status)
  262. if (menu_status >= 3){
  263. menu_status = 1;
  264. } else {
  265. menu_status += 1;
  266. }
  267. GM_setValue(`${Name}`, menu_status);
  268. registerMenuCommand(); // 重新注册脚本菜单
  269. if (document.getElementById('XIU2DarkMode')) {
  270. document.getElementById('XIU2DarkMode').remove(); // 即时修改样式
  271. addStyle();
  272. }
  273. //location.reload(); // 刷新网页
  274. };
  275.  
  276.  
  277. // 菜单开关
  278. function menu_switch(menu_status, Name, Tips) {
  279. if (menu_status == 'true'){
  280. GM_setValue(`${Name}`, false);
  281. GM_notification({text: `已关闭 [${Tips}] 功能\n(点击刷新网页后生效)`, timeout: 3500, onclick: function(){location.reload();}});
  282. }else{
  283. GM_setValue(`${Name}`, true);
  284. GM_notification({text: `已开启 [${Tips}] 功能\n(点击刷新网页后生效)`, timeout: 3500, onclick: function(){location.reload();}});
  285. }
  286. if (Name === 'menu_autoRecognition') {
  287. location.reload(); // 刷新网页
  288. }
  289. registerMenuCommand(); // 重新注册脚本菜单
  290. };
  291.  
  292.  
  293. // 返回菜单值
  294. function menu_value(menuName) {
  295. for (let menu of menu_ALL) {
  296. if (menu[0] == menuName) {
  297. return menu[3]
  298. }
  299. }
  300. }
  301.  
  302.  
  303. // 添加样式
  304. function addStyle() {
  305. let remove = false, style_Add = document.createElement('style'),
  306. hours = new Date().getHours(),
  307. style_10 = menu_value('menu_customMode1').split('|'),
  308. style_20 = menu_value('menu_customMode2').split('|'),
  309. style_30 = menu_value('menu_customMode3').split('|'),
  310. style = ``,
  311. style_00 = `html, body {background-color: #ffffff;}`,
  312. style_11 = `html {filter: brightness(${style_10[0]}%) !important;}`,
  313. style_11_firefox = `html {filter: brightness(${style_10[0]}%) !important; background-image: url();}`,
  314. style_12 = `html {filter: brightness(${style_10[1]}%) !important;}`,
  315. style_12_firefox = `html {filter: brightness(${style_10[1]}%) !important; background-image: url();}`,
  316. style_21 = `html {filter: brightness(${style_20[0]}%) sepia(${style_20[1]}%) !important;}`,
  317. style_21_firefox = `html {filter: brightness(${style_20[0]}%) sepia(${style_20[1]}%) !important; background-image: url();}`,
  318. style_22 = `html {filter: brightness(${style_20[2]}%) sepia(${style_20[3]}%) !important;}`,
  319. style_22_firefox = `html {filter: brightness(${style_20[2]}%) sepia(${style_20[3]}%) !important; background-image: url();}`,
  320. style_31 = `html {filter: invert(${style_30[0]}%) !important;} img, video {filter: invert(1) !important;}.mode-fullscreen video, img[alt="[公式]"] {filter: none !important;}`,
  321. style_31_firefox = `html {filter: invert(${style_30[0]}%) !important; background-image: url();} img, video {filter: invert(1) !important;} .mode-fullscreen video, img[alt="[公式]"] {filter: none !important;}`;
  322.  
  323. // Firefox 浏览器需要特殊对待
  324. if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
  325. style_11 = style_11_firefox
  326. style_12 = style_12_firefox
  327. style_21 = style_21_firefox
  328. style_22 = style_22_firefox
  329. style_31 = style_31_firefox
  330. }
  331.  
  332. // 白天(7点到19点)
  333. if (hours > 6 && hours < 19) {
  334. if (menu_value('menu_runDuringTheDay')) {
  335. style_12 = style_11
  336. style_22 = style_21
  337. } else {
  338. style_12 = style_22 = ''
  339. }
  340. }
  341.  
  342.  
  343. let darkModeType = getAutoSwitch();
  344.  
  345. switch(darkModeType) {
  346. case 1:
  347. style += style_12;
  348. break;
  349. case 2:
  350. style += style_22;
  351. break;
  352. case 3:
  353. style += style_31;
  354. break;
  355. }
  356. style_Add.id = 'XIU2DarkMode';
  357. style_Add.type = 'text/css';
  358. //console.log(document,document.lastElementChild,document.querySelector('html'))
  359. if (document.lastElementChild) {
  360. document.lastElementChild.appendChild(style_Add).textContent = style;
  361. } else { // 发现个别网站速度太慢的话,就会出现脚本运行太早,连 html 标签都还没加载。。。
  362. let timer1 = setInterval(function(){ // 每 5 毫秒检查一下 html 是否已存在
  363. if (document.lastElementChild) {
  364. clearInterval(timer1); // 取消定时器
  365. document.lastElementChild.appendChild(style_Add).textContent = style;
  366. }
  367. });
  368. }
  369.  
  370. let websiteList = [];
  371. if (menu_value('menu_autoRecognition')) { // 智能排除自带暗黑模式的网页 (beta)
  372. websiteList = menu_value('menu_forcedToEnable'); // 强制当前网站启用护眼模式
  373. }
  374.  
  375. // 为了避免 body 还没加载导致无法检查是否设置背景颜色
  376. let timer = setInterval(function(){ // 每 5 毫秒检查一下 body 是否已存在
  377. if (document.body) {
  378. clearInterval(timer); // 取消定时器(每 5 毫秒一次的)
  379. setTimeout(function(){ // 为了避免太快 body 的 CSS 还没加载上,先延迟 150 毫秒(缺点就是可能会出现短暂一闪而过的暗黑滤镜)
  380. console.log('[护眼模式] html:', window.getComputedStyle(document.lastElementChild).backgroundColor, 'body:', window.getComputedStyle(document.body).backgroundColor)
  381. if (window.getComputedStyle(document.body).backgroundColor === 'rgba(0, 0, 0, 0)' && window.getComputedStyle(document.lastElementChild).backgroundColor === 'rgba(0, 0, 0, 0)') {
  382. // 如果 body 没有 CSS 背景颜色,那就需要添加一个背景颜色,否则影响滤镜效果
  383. let style_Add2 = document.createElement('style');
  384. style_Add2.id = 'XIU2DarkMode2';
  385. document.lastElementChild.appendChild(style_Add2).textContent = style_00;
  386. } else if (window.getComputedStyle(document.body).backgroundColor === 'rgb(0, 0, 0)' || getColorValue(document.body) > 0 && getColorValue(document.body) < 898989 || getColorValue(document.lastElementChild) > 0 && getColorValue(document.lastElementChild) < 898989 || window.getComputedStyle(document.body).backgroundColor === 'rgba(0, 0, 0, 0)' && window.getComputedStyle(document.lastElementChild).backgroundColor === 'rgb(0, 0, 0)') {
  387. // 如果是黑色 (等于0,0,0) 或深色 (小于 89,89,89),就停用本脚本滤镜
  388. if (menu_value('menu_autoRecognition')) { // 排除自带暗黑模式的网页 (beta)
  389. for (let i=0;i<websiteList.length;i++){ // 这些网站强制启用护眼模式滤镜
  390. if (websiteList[i] === location.host) return
  391. }
  392. console.log('[护眼模式] 检测到当前网页自带暗黑模式,停用本脚本滤镜...')
  393. document.getElementById('XIU2DarkMode').remove();
  394. remove = true;
  395. }
  396. }
  397. }, 150);
  398.  
  399. // 用来解决一些 CSS 加载缓慢的网站,可能会出现没有正确排除的问题,在没有找到更好的办法之前,先这样凑活着用
  400. setTimeout(function(){
  401. console.log('[护眼模式] html:', window.getComputedStyle(document.lastElementChild).backgroundColor, 'body:', window.getComputedStyle(document.body).backgroundColor)
  402. if (window.getComputedStyle(document.body).backgroundColor === 'rgb(0, 0, 0)' || getColorValue(document.body) > 0 && getColorValue(document.body) < 898989 || getColorValue(document.lastElementChild) > 0 && getColorValue(document.lastElementChild) < 898989 || window.getComputedStyle(document.body).backgroundColor === 'rgba(0, 0, 0, 0)' && window.getComputedStyle(document.lastElementChild).backgroundColor === 'rgb(0, 0, 0)') {
  403. // 如果是黑色 (等于0,0,0) 或深色 (小于 89,89,89),就停用本脚本滤镜
  404. if (menu_value('menu_autoRecognition')) { // 排除自带暗黑模式的网页 (beta)
  405. for (let i=0;i<websiteList.length;i++){ // 这些网站强制启用护眼模式滤镜
  406. if (websiteList[i] === location.host) return
  407. }
  408. if (remove) return
  409. console.log('[护眼模式] 检测到当前网页自带暗黑模式,停用本脚本滤镜...')
  410. if (document.getElementById('XIU2DarkMode')) document.getElementById('XIU2DarkMode').remove();
  411. if (document.getElementById('XIU2DarkMode2')) document.getElementById('XIU2DarkMode2').remove();
  412. }
  413. }
  414. }, 1500);
  415. }
  416. });
  417.  
  418. // 用来解决一些 CSS 加载缓慢的网站,可能会出现没有正确排除的问题,在没有找到更好的办法之前,先这样凑活着用
  419. /*setTimeout(function(){
  420. console.log('[护眼模式] html:', window.getComputedStyle(document.lastElementChild).backgroundColor, 'body:', window.getComputedStyle(document.body).backgroundColor)
  421. if (window.getComputedStyle(document.body).backgroundColor === 'rgb(0, 0, 0)' || getColorValue(document.body) > 0 && getColorValue(document.body) < 898989 || getColorValue(document.lastElementChild) > 0 && getColorValue(document.lastElementChild) < 898989) {
  422. // 如果是黑色 (等于0,0,0) 或深色 (小于 89,89,89),就停用本脚本滤镜
  423. if (menu_value('menu_autoRecognition')) { // 排除自带暗黑模式的网页 (beta)
  424. for (let i=0;i<websiteList.length;i++){ // 这些网站强制启用护眼模式滤镜
  425. if (websiteList[i] === location.host) return
  426. }
  427. if (remove) return
  428. console.log('[护眼模式] 检测到当前网页自带暗黑模式,停用本脚本滤镜...')
  429. if (document.getElementById('XIU2DarkMode')) document.getElementById('XIU2DarkMode').remove();
  430. if (document.getElementById('XIU2DarkMode2')) document.getElementById('XIU2DarkMode2').remove();
  431. }
  432. }
  433. }, 3000);*/
  434.  
  435. // 解决远景论坛会清理掉前面插入的 CSS 样式的问题
  436. if (location.hostname === 'bbs.pcbeta.com') {
  437. let timer1 = setInterval(function(){
  438. if (!document.getElementById('XIU2DarkMode')) {
  439. document.lastElementChild.appendChild(style_Add).textContent = style;
  440. clearInterval(timer1);
  441. }
  442. });
  443. }
  444. }
  445.  
  446. // 获取背景颜色值
  447. function getColorValue(e) {
  448. let rgbValueArry = window.getComputedStyle(e).backgroundColor.replace(/rgba|rgb|\(|\)| /g, '').split (',')
  449. return parseInt(rgbValueArry[0] + rgbValueArry[1] + rgbValueArry[2])
  450. }
  451. })();