夜间模式助手

实现任意网站的夜间模式,支持网站白名单

目前为 2022-03-23 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name 夜间模式助手
  3. // @namespace https://github.com/syhyz1990/darkmode
  4. // @version 2.1.4
  5. // @icon https://www.youxiaohou.com/darkmode.png
  6. // @description 实现任意网站的夜间模式,支持网站白名单
  7. // @author YouXiaoHou
  8. // @license MIT
  9. // @homepage https://www.youxiaohou.com/tool/install-darkmode.html
  10. // @supportURL https://github.com/syhyz1990/darkmode
  11. // @match *://*/*
  12. // @require https://unpkg.com/darkrule@latest/dist/rule.min.js
  13. // @require https://unpkg.com/sweetalert2@10.16.6/dist/sweetalert2.min.js
  14. // @resource swalStyle https://unpkg.com/sweetalert2@10.16.6/dist/sweetalert2.min.css
  15. // @run-at document-start
  16. // @grant GM_getValue
  17. // @grant GM_setValue
  18. // @grant GM_registerMenuCommand
  19. // @grant GM_getResourceText
  20. // ==/UserScript==
  21.  
  22. ;(function () {
  23. 'use strict';
  24.  
  25. let util = {
  26. getValue(name) {
  27. return GM_getValue(name);
  28. },
  29.  
  30. setValue(name, value) {
  31. GM_setValue(name, value);
  32. },
  33.  
  34. addStyle(id, tag, css) {
  35. tag = tag || 'style';
  36. let doc = document, styleDom = doc.getElementById(id);
  37. if (styleDom) return;
  38. let style = doc.createElement(tag);
  39. style.rel = 'stylesheet';
  40. style.id = id;
  41. tag === 'style' ? style.innerHTML = css : style.href = css;
  42. doc.head.appendChild(style);
  43. },
  44.  
  45. addThemeColor(color) {
  46. let doc = document, meta = doc.getElementsByName('theme-color')[0];
  47. if (meta) return meta.setAttribute('content', color);
  48. let metaEle = doc.createElement('meta');
  49. metaEle.name = 'theme-color';
  50. metaEle.content = color;
  51. doc.head.appendChild(metaEle);
  52. },
  53.  
  54. getThemeColor() {
  55. let meta = document.getElementsByName('theme-color')[0];
  56. if (meta) {
  57. return meta.content;
  58. }
  59. return '#ffffff';
  60. },
  61.  
  62. removeElementById(eleId) {
  63. let ele = document.getElementById(eleId);
  64. ele && ele.parentNode.removeChild(ele);
  65. },
  66.  
  67. hasElementById(eleId) {
  68. return document.getElementById(eleId);
  69. },
  70.  
  71. filter: '-webkit-filter: url(#dark-mode-filter) !important; filter: url(#dark-mode-filter) !important;',
  72. reverseFilter: '-webkit-filter: url(#dark-mode-reverse-filter) !important; filter: url(#dark-mode-reverse-filter) !important;',
  73. firefoxFilter: `filter: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"><filter id="dark-mode-filter" color-interpolation-filters="sRGB"><feColorMatrix type="matrix" values="0.283 -0.567 -0.567 0 0.925 -0.567 0.283 -0.567 0 0.925 -0.567 -0.567 0.283 0 0.925 0 0 0 1 0"/></filter></svg>#dark-mode-filter') !important;`,
  74. firefoxReverseFilter: `filter: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"><filter id="dark-mode-reverse-filter" color-interpolation-filters="sRGB"><feColorMatrix type="matrix" values="0.333 -0.667 -0.667 0 1 -0.667 0.333 -0.667 0 1 -0.667 -0.667 0.333 0 1 0 0 0 1 0"/></filter></svg>#dark-mode-reverse-filter') !important;`,
  75. noneFilter: '-webkit-filter: none !important; filter: none !important;',
  76. };
  77.  
  78. let main = {
  79. /**
  80. * 配置默认值
  81. */
  82. initValue() {
  83. let value = [{
  84. name: 'dark_mode',
  85. value: 'light'
  86. }, {
  87. name: 'button_position',
  88. value: 'left'
  89. }, {
  90. name: 'button_size',
  91. value: 30
  92. }, {
  93. name: 'exclude_list',
  94. value: ['youku.com', 'v.youku.com', 'www.douyu.com', 'www.iqiyi.com', 'vip.iqiyi.com', 'mail.qq.com', 'live.kuaishou.com']
  95. }, {
  96. name: 'origin_theme_color',
  97. value: '#ffffff'
  98. }];
  99.  
  100. value.forEach((v) => {
  101. util.getValue(v.name) === undefined && util.setValue(v.name, v.value);
  102. });
  103. },
  104.  
  105. addExtraStyle() {
  106. try {
  107. return darkModeRule;
  108. } catch (e) {
  109. return '';
  110. }
  111. },
  112.  
  113. createDarkFilter() {
  114. if (util.hasElementById('dark-mode-svg')) return;
  115. let svgDom = '<svg id="dark-mode-svg" style="height: 0; width: 0;"><filter id="dark-mode-filter" x="0" y="0" width="99999" height="99999"><feColorMatrix type="matrix" values="0.283 -0.567 -0.567 0 0.925 -0.567 0.283 -0.567 0 0.925 -0.567 -0.567 0.283 0 0.925 0 0 0 1 0"></feColorMatrix></filter><filter id="dark-mode-reverse-filter" x="0" y="0" width="99999" height="99999"><feColorMatrix type="matrix" values="0.333 -0.667 -0.667 0 1 -0.667 0.333 -0.667 0 1 -0.667 -0.667 0.333 0 1 0 0 0 1 0"></feColorMatrix></filter></svg>';
  116. let div = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
  117. div.innerHTML = svgDom;
  118. let frag = document.createDocumentFragment();
  119. while (div.firstChild)
  120. frag.appendChild(div.firstChild);
  121. document.head.appendChild(frag);
  122. },
  123.  
  124. createDarkStyle() {
  125. util.addStyle('dark-mode-style', 'style', `
  126. @media screen {
  127. html {
  128. ${this.isFirefox() ? util.firefoxFilter : util.filter}
  129. scrollbar-color: #454a4d #202324;
  130. }
  131. /* Default Reverse rule */
  132. img,
  133. video,
  134. iframe,
  135. canvas,
  136. :not(object):not(body) > embed,
  137. object,
  138. svg image,
  139. [style*="background:url"],
  140. [style*="background-image:url"],
  141. [style*="background: url"],
  142. [style*="background-image: url"],
  143. [background],
  144. twitterwidget,
  145. .sr-reader,
  146. .no-dark-mode,
  147. .sr-backdrop {
  148. ${this.isFirefox() ? util.firefoxReverseFilter : util.reverseFilter}
  149. }
  150. [style*="background:url"] *,
  151. [style*="background-image:url"] *,
  152. [style*="background: url"] *,
  153. [style*="background-image: url"] *,
  154. input,
  155. [background] *,
  156. img[src^="https://s0.wp.com/latex.php"],
  157. twitterwidget .NaturalImage-image {
  158. ${util.noneFilter}
  159. }
  160. /* Text contrast */
  161. html {
  162. text-shadow: 0 0 0 !important;
  163. }
  164. /* Full screen */
  165. .no-filter,
  166. :-webkit-full-screen,
  167. :-webkit-full-screen *,
  168. :-moz-full-screen,
  169. :-moz-full-screen *,
  170. :fullscreen,
  171. :fullscreen * {
  172. ${util.noneFilter}
  173. }
  174. ::-webkit-scrollbar {
  175. background-color: #202324;
  176. color: #aba499;
  177. }
  178. ::-webkit-scrollbar-thumb {
  179. background-color: #454a4d;
  180. }
  181. ::-webkit-scrollbar-thumb:hover {
  182. background-color: #575e62;
  183. }
  184. ::-webkit-scrollbar-thumb:active {
  185. background-color: #484e51;
  186. }
  187. ::-webkit-scrollbar-corner {
  188. background-color: #181a1b;
  189. }
  190. /* Page background */
  191. html {
  192. background: #fff !important;
  193. }
  194. ${this.addExtraStyle()}
  195. }
  196. @media print {
  197. .no-print {
  198. display: none !important;
  199. }
  200. }`);
  201. },
  202.  
  203. setThemeColor() {
  204. util.setValue('origin_theme_color', util.getThemeColor());
  205. },
  206.  
  207. enableDarkMode() {
  208. if (this.isFullScreen()) return;
  209. !this.isFirefox() && this.createDarkFilter();
  210. this.createDarkStyle();
  211. util.addThemeColor('#131313')
  212. },
  213.  
  214. disableDarkMode() {
  215. util.removeElementById('dark-mode-svg');
  216. util.removeElementById('dark-mode-style');
  217. util.addThemeColor(util.getValue('origin_theme_color'))
  218. },
  219.  
  220. addButton() {
  221. if (this.isTopWindow()) {
  222. let lightIcon = `<div style="background: #000;display: flex;align-items: center;justify-content: center;width: ${util.getValue('button_size')}px;height: ${util.getValue('button_size')}px;border-radius: 50%"><svg style="position: static;margin: 0;padding: 0;" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="${util.getValue('button_size') / 1.5}" height="${util.getValue('button_size') / 1.5}"><path d="M522.88 874.667A21.333 21.333 0 0 1 544.213 896v85.333a21.333 21.333 0 0 1-21.333 21.334h-21.333a21.333 21.333 0 0 1-21.334-21.334V896a21.333 21.333 0 0 1 21.334-21.333h21.333zm268.416-107.52l60.352 60.352a21.333 21.333 0 0 1 0 30.165l-15.083 15.083a21.333 21.333 0 0 1-30.186 0l-60.331-60.352a21.333 21.333 0 0 1 0-30.166l15.083-15.082a21.333 21.333 0 0 1 30.165 0zm-527.957 0l15.082 15.082a21.333 21.333 0 0 1 0 30.166l-60.352 60.352a21.333 21.333 0 0 1-30.165 0l-15.083-15.083a21.333 21.333 0 0 1 0-30.165l60.331-60.352a21.333 21.333 0 0 1 30.187 0zM512 277.333c141.376 0 256 114.624 256 256s-114.624 256-256 256-256-114.624-256-256 114.624-256 256-256zm0 64a192 192 0 1 0 0 384 192 192 0 0 0 0-384zm448.213 160a21.333 21.333 0 0 1 21.334 21.334V544a21.333 21.333 0 0 1-21.334 21.333H874.88A21.333 21.333 0 0 1 853.547 544v-21.333a21.333 21.333 0 0 1 21.333-21.334h85.333zm-810.666 0a21.333 21.333 0 0 1 21.333 21.334V544a21.333 21.333 0 0 1-21.333 21.333H64.213A21.333 21.333 0 0 1 42.88 544v-21.333a21.333 21.333 0 0 1 21.333-21.334h85.334zm687.04-307.413l15.082 15.083a21.333 21.333 0 0 1 0 30.165l-60.352 60.352a21.333 21.333 0 0 1-30.165 0l-15.083-15.083a21.333 21.333 0 0 1 0-30.165L806.4 193.92a21.333 21.333 0 0 1 30.187 0zm-618.496 0l60.352 60.352a21.333 21.333 0 0 1 0 30.165L263.36 299.52a21.333 21.333 0 0 1-30.187 0l-60.352-60.373a21.333 21.333 0 0 1 0-30.166l15.083-15.082a21.333 21.333 0 0 1 30.165 0zM522.9 64a21.333 21.333 0 0 1 21.334 21.333v85.334A21.333 21.333 0 0 1 522.9 192h-21.333a21.333 21.333 0 0 1-21.333-21.333V85.333A21.333 21.333 0 0 1 501.568 64h21.333z" fill="#fff"/></svg></div>`,
  223. darkIcon = `<div style="background: #333;display: flex;align-items: center;justify-content: center;width: ${util.getValue('button_size')}px;height: ${util.getValue('button_size')}px;border-radius: 50%"><svg style="position: static;margin: 0;padding: 0;" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="${util.getValue('button_size') / 1.5}" height="${util.getValue('button_size') / 1.5}"><path d="M513.173 128A255.061 255.061 0 0 0 448 298.667c0 141.376 114.624 256 256 256a255.36 255.36 0 0 0 189.803-84.203A392.855 392.855 0 0 1 896 512c0 212.075-171.925 384-384 384S128 724.075 128 512c0-209.707 168.107-380.16 376.96-383.936l8.192-.064zM395.35 213.93l-3.52 1.409C274.645 262.827 192 377.77 192 512c0 176.725 143.275 320 320 320 145.408 0 268.16-96.981 307.115-229.803l1.536-5.504-1.6.64a319.51 319.51 0 0 1-106.496 21.227l-8.555.107c-176.725 0-320-143.275-320-320 0-28.48 3.755-56.406 10.944-83.2l.405-1.536z" fill="#adbac7"/></svg></div>`;
  224.  
  225. let o = document.createElement('div'),
  226. buttonPostion = util.getValue('button_position');
  227. o.style.position = 'fixed';
  228. o.style[buttonPostion] = '25px';
  229. o.style.bottom = '25px';
  230. o.style.cursor = 'pointer';
  231. o.style.zIndex = '2147483999';
  232. o.style.userSelect = 'none';
  233. o.className = 'no-print';
  234. o.id = 'darkBtn';
  235. this.isDarkMode() ? o.innerHTML = lightIcon : o.innerHTML = darkIcon;
  236. document.body.appendChild(o);
  237.  
  238. o.addEventListener("click", () => {
  239. if (this.isDarkMode()) { //黑暗模式变为正常模式
  240. util.setValue('dark_mode', 'light');
  241. o.innerHTML = darkIcon;
  242. this.disableDarkMode();
  243. } else {
  244. util.setValue('dark_mode', 'dark');
  245. o.innerHTML = lightIcon;
  246. this.enableDarkMode();
  247. }
  248. });
  249. }
  250. },
  251.  
  252. registerMenuCommand() {
  253. if (this.isTopWindow()) {
  254. let whiteList = util.getValue('exclude_list');
  255. let host = location.host;
  256. if (whiteList.includes(host)) {
  257. GM_registerMenuCommand('💡 当前网站:❌', () => {
  258. let index = whiteList.indexOf(host);
  259. whiteList.splice(index, 1);
  260. util.setValue('exclude_list', whiteList);
  261. history.go(0);
  262. });
  263. } else {
  264. GM_registerMenuCommand('💡 当前网站:✔️', () => {
  265. whiteList.push(host);
  266. util.setValue('exclude_list', Array.from(new Set(whiteList)));
  267. history.go(0);
  268. });
  269. }
  270.  
  271. GM_registerMenuCommand('⚙️ 设置', () => {
  272. let style = `
  273. .darkmode-popup { font-size: 14px !important; }
  274. .darkmode-center { display: flex;align-items: center; }
  275. .darkmode-setting-label { display: flex;align-items: center;justify-content: space-between;padding-top: 15px; }
  276. .darkmode-setting-label-col { display: flex;align-items: flex-start;;padding-top: 15px;flex-direction:column }
  277. .darkmode-setting-radio { width: 16px;height: 16px; }
  278. .darkmode-setting-textarea { width: 100%; margin: 14px 0 0; height: 100px; resize: none; border: 1px solid #bbb; box-sizing: border-box; padding: 5px 10px; border-radius: 5px; color: #666; line-height: 1.2; }
  279. .darkmode-setting-input { border: 1px solid #bbb; box-sizing: border-box; padding: 5px 10px; border-radius: 5px; width: 100px}
  280. `;
  281. util.addStyle('darkmode-style', 'style', style);
  282. util.addStyle('swal-pub-style', 'style', GM_getResourceText('swalStyle'));
  283. let excludeListStr = util.getValue('exclude_list').join('\n');
  284.  
  285. let dom = `<div style="font-size: 1em;">
  286. <label class="darkmode-setting-label">按钮位置 <div id="S-Dark-Position" class="darkmode-center"><input type="radio" name="buttonPosition" ${util.getValue('button_position') === 'left' ? 'checked' : ''} class="darkmode-setting-radio" value="left">左 <input type="radio" name="buttonPosition" style="margin-left: 30px;" ${util.getValue('button_position') === 'right' ? 'checked' : ''} class="darkmode-setting-radio" value="right">右</div></label>
  287. <label class="darkmode-setting-label"><span style="text-align: left;">按钮大小(默认:30)<small id="currentSize">当前:${util.getValue('button_size')}</small></span>
  288. <input id="S-Dark-Size" type="range" class="darkmode-setting-range" min="20" max="50" step="2" value="${util.getValue('button_size')}">
  289. </label>
  290. <label class="darkmode-setting-label-col">排除下列网址 <textarea placeholder="列表中的域名将不开启夜间模式,一行一个,例如:v.youku.com" id="S-Dark-Exclude" class="darkmode-setting-textarea">${excludeListStr}</textarea></label>
  291. </div>`;
  292. Swal.fire({
  293. title: '夜间模式配置',
  294. html: dom,
  295. icon: 'info',
  296. showCloseButton: true,
  297. confirmButtonText: '保存',
  298. footer: '<div style="text-align: center;font-size: 1em;">点击查看 <a href="https://www.youxiaohou.com/tool/install-darkmode.html" target="_blank">使用说明</a>,助手免费开源,Powered by <a href="https://www.youxiaohou.com">油小猴</a></div>',
  299. customClass: {
  300. popup: 'darkmode-popup',
  301. },
  302. }).then((res) => {
  303. res.isConfirmed && history.go(0);
  304. });
  305.  
  306. document.getElementById('S-Dark-Position').addEventListener('click', (e) => {
  307. e.target.tagName === "INPUT" && util.setValue('button_position', e.target.value);
  308. });
  309. document.getElementById('S-Dark-Size').addEventListener('change', (e) => {
  310. util.setValue('button_size', e.currentTarget.value);
  311. document.getElementById('currentSize').innerText = '当前:' + e.currentTarget.value;
  312. });
  313. document.getElementById('S-Dark-Exclude').addEventListener('change', (e) => {
  314. util.setValue('exclude_list', Array.from(new Set(e.currentTarget.value.split('\n').filter(Boolean))));
  315. });
  316. });
  317. }
  318. },
  319.  
  320. isTopWindow() {
  321. return window.self === window.top;
  322. },
  323.  
  324. addListener() {
  325. document.addEventListener("fullscreenchange", (e) => {
  326. if (this.isFullScreen()) {
  327. //进入全屏
  328. this.disableDarkMode();
  329. } else {
  330. //退出全屏
  331. this.isDarkMode() && this.enableDarkMode();
  332. }
  333. });
  334. },
  335.  
  336. isDarkMode() {
  337. return util.getValue('dark_mode') === 'dark';
  338. },
  339.  
  340. isInExcludeList() {
  341. return util.getValue('exclude_list').includes(location.host);
  342. },
  343.  
  344. isFullScreen() {
  345. return document.fullscreenElement;
  346. },
  347.  
  348. isFirefox() {
  349. return /Firefox/i.test(navigator.userAgent);
  350. },
  351.  
  352. firstEnableDarkMode() {
  353. if (document.head) {
  354. this.isDarkMode() && this.enableDarkMode();
  355. }
  356. const headObserver = new MutationObserver(() => {
  357. this.isDarkMode() && this.enableDarkMode();
  358. });
  359. headObserver.observe(document.head, {childList: true, subtree: true});
  360.  
  361. if (document.body) {
  362. this.addButton();
  363. } else {
  364. const bodyObserver = new MutationObserver(() => {
  365. if (document.body) {
  366. bodyObserver.disconnect();
  367. this.addButton();
  368. }
  369. });
  370. bodyObserver.observe(document, {childList: true, subtree: true});
  371. }
  372. },
  373.  
  374. init() {
  375. this.initValue();
  376. this.setThemeColor();
  377. this.registerMenuCommand();
  378. if (this.isInExcludeList()) return;
  379. this.addListener();
  380. this.firstEnableDarkMode();
  381. }
  382. };
  383. main.init();
  384. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址