Greasy Fork is available in English.
实现任意网站的夜间模式,自动跳过原生暗色网站
// ==UserScript== // @name 智能夜间模式 // @version 1.0.0 // @description 实现任意网站的夜间模式,自动跳过原生暗色网站 // @author Phoebe // @license MIT // @match *://*/* // @require https://unpkg.com/[email protected]/dist/rule.min.js // @run-at document-start // @grant GM_getValue // @grant GM_setValue // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48cGF0aCBkPSJNOTMuNSA5NC42YzEwLjYgMCAyMC4zLTMuMyAyOC4yLTktOC4zIDIyLjUtMzAuMiAzOC42LTU2IDM4LjYtMzIuNyAwLTU5LjMtMjUuOC01OS4zLTU3LjdTMzIuOSA4LjcgNjUuNyA4LjdoMi4yQzU0LjYgMTcgNDUuNyAzMS41IDQ1LjcgNDhjMCAyNS43IDIxLjQgNDYuNiA0Ny44IDQ2LjZ6IiBmaWxsPSIjZmZiNTc4Ii8+PHBhdGggZD0iTTEyMS42IDgxLjhjLS44IDAtMS42LjItMi4zLjctNy41IDUuMy0xNi41IDguMS0yNS44IDguMS0yNC4yIDAtNDMuOS0xOS4xLTQzLjktNDIuNyAwLTE0LjcgNy42LTI4LjEgMjAuMy0zNiAxLjEtLjcgMS44LTEuOSAxLjgtMy4yYTMuOCAzLjggMCAwIDAtMy44LTMuOGgtMi4zQzMwLjggNC45IDIuNSAzMi41IDIuNSA2Ni41UzMwLjggMTI4IDY1LjcgMTI4YzI2LjcgMCA1MC43LTE2LjUgNTkuNi00MSAuMS0uNC4yLS45LjItMS4zIDAtMi4xLTEuNy0zLjktMy45LTMuOXptLTU1LjkgMzguNWMtMzAuNSAwLTU1LjQtMjQuMi01NS40LTUzLjkgMC0yNi4yIDE5LjQtNDguNiA0NS43LTUzLjEtOSA5LjQtMTQuMiAyMS44LTE0LjIgMzQuNyAwIDI3LjggMjMuMiA1MC40IDUxLjcgNTAuNCA2LjcgMCAxMy4yLTEuMiAxOS4zLTMuNi0xMCAxNS44LTI3LjggMjUuNS00Ny4xIDI1LjV6bTM1LjYtNDcuOUg3Ny45Yy0xLjYgMC0yLjktMS4zLTIuOS0yLjkgMC0xIC41LTEuOSAxLjMtMi40TDkxLjYgNTdINzcuOWMtMS42IDAtMi45LTEuMy0yLjktMi45czEuMy0yLjkgMi45LTIuOWgyMy40YzEuNiAwIDIuOSAxLjMgMi45IDIuOSAwIDEtLjUgMS45LTEuMyAyLjRMODcuNiA2Ni42aDEzLjdjMS42IDAgMi45IDEuMyAyLjkgMi45cy0xLjMgMi45LTIuOSAyLjl6bTEzLjItMzEuMWgtMTQuNGMtMS42IDAtMi45LTEuMy0yLjktMi45IDAtMSAuNS0xLjkgMS4zLTIuNGw2LjMtNC4xaC00LjdjLTEuNiAwLTIuOS0xLjMtMi45LTIuOXMxLjMtMi45IDIuOS0yLjloMTQuNGMxLjYgMCAyLjkgMS4zIDIuOSAyLjkgMCAxLS41IDEuOS0xLjMgMi40bC02LjMgNC4xaDQuN2MxLjYgMCAyLjkgMS4zIDIuOSAyLjlzLTEuMyAyLjktMi45IDIuOXptNS42LTI3LjVIMTA4Yy0xLjYgMC0yLjktMS4zLTIuOS0yLjkgMC0xIC41LTEuOSAxLjMtMi40bDQuMS0yLjdIMTA4Yy0xLjYgMC0yLjktMS4zLTIuOS0yLjlTMTA2LjQgMCAxMDggMGgxMi4xYzEuNiAwIDIuOSAxLjMgMi45IDIuOSAwIDEtLjUgMS45LTEuMyAyLjRMMTE3LjYgOGgyLjRjMS42IDAgMi45IDEuMyAyLjkgMi45cy0xLjIgMi45LTIuOCAyLjl6IiBmaWxsPSIjNDQ0Ii8+PC9zdmc+ // @namespace http://greasyfork.icu/users/1292139 // ==/UserScript== ;(function () { 'use strict'; let util = { getValue(name) { return GM_getValue(name); }, setValue(name, value) { GM_setValue(name, value); }, addStyle(id, tag, css) { tag = tag || 'style'; let doc = document, styleDom = doc.getElementById(id); if (styleDom) return; let style = doc.createElement(tag); style.rel = 'stylesheet'; style.id = id; tag === 'style' ? style.innerHTML = css : style.href = css; doc.head.appendChild(style); }, hover(ele, fn1, fn2) { ele.onmouseenter = function () { //移入事件 fn1.call(ele); }; ele.onmouseleave = function () { //移出事件 fn2.call(ele); }; }, addThemeColor(color) { let doc = document, meta = doc.getElementsByName('theme-color')[0]; if (meta) return meta.setAttribute('content', color); let metaEle = doc.createElement('meta'); metaEle.name = 'theme-color'; metaEle.content = color; doc.head.appendChild(metaEle); }, getThemeColor() { let meta = document.getElementsByName('theme-color')[0]; if (meta) { return meta.content; } return '#ffffff'; }, removeElementById(eleId) { let ele = document.getElementById(eleId); ele && ele.parentNode.removeChild(ele); }, hasElementById(eleId) { return document.getElementById(eleId); }, filter: '-webkit-filter: url(#dark-mode-filter) !important; filter: url(#dark-mode-filter) !important;', reverseFilter: '-webkit-filter: url(#dark-mode-reverse-filter) !important; filter: url(#dark-mode-reverse-filter) !important;', 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;`, 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;`, noneFilter: '-webkit-filter: none !important; filter: none !important;', }; let nativeDark = { cacheKey() { return 'native_dark_cache_v2:' + location.host; }, cacheTTL: 7 * 24 * 60 * 60 * 1000, readCache() { let data = util.getValue(this.cacheKey()); if (!data || !data.time || Date.now() - data.time > this.cacheTTL) return null; return data.value === true; }, writeCache(value) { util.setValue(this.cacheKey(), { value: value === true, time: Date.now() }); }, parseColor(element) { if (!element) return null; let color = window.getComputedStyle(element).backgroundColor; let match = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([.\d]+))?/); if (!match) return null; if (match[4] !== undefined && Number(match[4]) === 0) return null; return { r: Number(match[1]), g: Number(match[2]), b: Number(match[3]) }; }, luminance(color) { return color ? ((color.r * 299) + (color.g * 587) + (color.b * 114)) / 1000 : 255; }, isDarkColor(color) { return this.luminance(color) < 96; }, isLightColor(color) { return this.luminance(color) > 170; }, hasDarkToken(value) { return /(^|[\s_-])dark($|[\s_-])/.test(String(value || '').toLowerCase()); }, hasExplicitDarkTheme(element) { if (!element) return false; return this.hasDarkToken(element.className) || this.hasDarkToken(element.dataset && element.dataset.darkTheme) || this.hasDarkToken(element.dataset && element.dataset.theme) || this.hasDarkToken(element.dataset && element.dataset.colorMode); }, visibleBackgroundColorAt(x, y) { let element = document.elementFromPoint(x, y); while (element) { let color = this.parseColor(element); if (color) return color; element = element.parentElement; } return this.parseColor(document.body) || this.parseColor(document.documentElement); }, visiblePageLooksDark() { if (!document.body || !document.documentElement || !document.elementFromPoint) return false; let width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); let height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); let points = [ [0.5, 0.12], [0.25, 0.28], [0.5, 0.28], [0.75, 0.28], [0.25, 0.5], [0.5, 0.5], [0.75, 0.5], [0.25, 0.78], [0.5, 0.78], [0.75, 0.78] ]; let dark = 0; let light = 0; let detected = 0; for (let point of points) { let color = this.visibleBackgroundColorAt(Math.floor(width * point[0]), Math.floor(height * point[1])); if (!color) continue; detected += 1; if (this.isDarkColor(color)) dark += 1; if (this.isLightColor(color)) light += 1; } return detected > 0 && dark / detected >= 0.7 && light === 0; }, pageLooksNativeDark() { let visiblyDark = this.visiblePageLooksDark(); if (!visiblyDark) return false; let colorScheme = window.getComputedStyle(document.documentElement).colorScheme; if (this.hasExplicitDarkTheme(document.documentElement) || this.hasExplicitDarkTheme(document.body) || /^\s*dark\s*$/i.test(colorScheme)) { return true; } return true; }, detect() { let cached = this.readCache(); if (cached !== null) return cached; let result = this.pageLooksNativeDark(); this.writeCache(result); return result; } }; let main = { /** * 配置默认值 */ initValue() { let value = [{ name: 'dark_mode', value: 'dark' }, { name: 'exclude_list', value: ['youku.com', 'v.youku.com', 'www.douyu.com', 'www.iqiyi.com', 'vip.iqiyi.com', 'mail.qq.com', 'live.kuaishou.com'] }, { name: 'origin_theme_color', value: '#ffffff' }]; value.forEach((v) => { util.getValue(v.name) === undefined && util.setValue(v.name, v.value); }); }, addExtraStyle() { try { return darkModeRule; } catch (e) { return ''; } }, createDarkFilter() { if (util.hasElementById('dark-mode-svg')) return; 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>'; let div = document.createElementNS('http://www.w3.org/1999/xhtml', 'div'); div.innerHTML = svgDom; let frag = document.createDocumentFragment(); while (div.firstChild) frag.appendChild(div.firstChild); document.head.appendChild(frag); }, createDarkStyle() { util.addStyle('dark-mode-style', 'style', ` @media screen { html { ${this.isFirefox() ? util.firefoxFilter : util.filter} scrollbar-color: #454a4d #202324; } /* Default Reverse rule */ img, video, iframe, canvas, :not(object):not(body) > embed, object, svg image, [style*="background:url"], [style*="background-image:url"], [style*="background: url"], [style*="background-image: url"], [background], twitterwidget, .sr-reader, .no-dark-mode, .sr-backdrop { ${this.isFirefox() ? util.firefoxReverseFilter : util.reverseFilter} } [style*="background:url"] *, [style*="background-image:url"] *, [style*="background: url"] *, [style*="background-image: url"] *, input, [background] *, img[src^="https://s0.wp.com/latex.php"], twitterwidget .NaturalImage-image { ${util.noneFilter} } /* Text contrast */ html { text-shadow: 0 0 0 !important; } /* Full screen */ .no-filter, :-webkit-full-screen, :-webkit-full-screen *, :-moz-full-screen, :-moz-full-screen *, :fullscreen, :fullscreen * { ${util.noneFilter} } ::-webkit-scrollbar { background-color: #202324; color: #aba499; } ::-webkit-scrollbar-thumb { background-color: #454a4d; } ::-webkit-scrollbar-thumb:hover { background-color: #575e62; } ::-webkit-scrollbar-thumb:active { background-color: #484e51; } ::-webkit-scrollbar-corner { background-color: #181a1b; } /* Page background */ html { background: #fff !important; } ${this.addExtraStyle()} } @media print { .no-print { display: none !important; } }`); }, setThemeColor() { util.setValue('origin_theme_color', util.getThemeColor()); }, enableDarkMode() { if (this.isFullScreen()) return; if (this.shouldSkipNativeDark()) return; !this.isFirefox() && this.createDarkFilter(); this.createDarkStyle(); util.addThemeColor('#131313'); this.scheduleNativeDarkCheck(); }, disableDarkMode() { util.removeElementById('dark-mode-svg'); util.removeElementById('dark-mode-style'); util.addThemeColor(util.getValue('origin_theme_color')); }, shouldSkipNativeDark() { let cached = nativeDark.readCache(); if (cached === true) { this.disableDarkMode(); return true; } if (cached === false) return false; return false; }, scheduleNativeDarkCheck() { if (this.nativeDarkCheckStarted) return; this.nativeDarkCheckStarted = true; let run = () => setTimeout(() => this.verifyNativeDarkMode(), 150); if (document.body) { run(); return; } const observer = new MutationObserver(() => { if (!document.body) return; observer.disconnect(); run(); }); observer.observe(document.documentElement, {childList: true, subtree: true}); }, verifyNativeDarkMode() { if (nativeDark.readCache() !== null) return; const root = document.documentElement; const oldVisibility = root.style.getPropertyValue('visibility'); const oldVisibilityPriority = root.style.getPropertyPriority('visibility'); root.style.setProperty('visibility', 'hidden', 'important'); this.disableDarkMode(); const isNativeDark = nativeDark.detect(); if (!isNativeDark && !this.isInExcludeList()) { !this.isFirefox() && this.createDarkFilter(); this.createDarkStyle(); util.addThemeColor('#131313'); } requestAnimationFrame(() => { if (oldVisibility) { root.style.setProperty('visibility', oldVisibility, oldVisibilityPriority); } else { root.style.removeProperty('visibility'); } }); }, isTopWindow() { return window.self === window.top; }, addListener() { document.addEventListener("fullscreenchange", (e) => { if (this.isFullScreen()) { //进入全屏 this.disableDarkMode(); } else { //退出全屏 this.enableDarkMode(); } }); }, isDarkMode() { return true; }, isInExcludeList() { return util.getValue('exclude_list').includes(location.host); }, isFullScreen() { return document.fullscreenElement; }, isFirefox() { return /Firefox/i.test(navigator.userAgent); }, firstEnableDarkMode() { if (document.head) { this.enableDarkMode(); } const headObserver = new MutationObserver(() => { this.enableDarkMode(); }); headObserver.observe(document.head, {childList: true, subtree: true}); }, init() { this.initValue(); this.setThemeColor(); if (this.isInExcludeList()) return; this.addListener(); this.firstEnableDarkMode(); } }; main.init(); })();