Mfuns_Dark_Mode的技术解析

接上一个文章,来对这个js做一些简单的解析。

原理其实很简单,因为我懒得去一个一个css的去适配所以采用了一个很简单的方法,直接把所有的白色元素替换成黑的就可以了,然后对文字颜色进行反转,但是这样就会出现一个问题那就是子元素没办法即使替换,本来想过10ms替换一下但这样会导致站点原有的动画不停循环所以我写的是鼠标点击一次就进行一次替换以及在页面第一次加载也进行一次替换。

Script==<br>// @name         喵御宅深色模式-Mfuns Dark Mode<br>// @namespace    https://moongs.asia/<br>// @version      0.01<br>// @description  安装后自动将Mfuns变为深色模式<br>// @author       Sakurazuki<br>// @match        https://www.mfuns.net/*<br>// @match        https://mfuns.net/*<br>// @grant        none<br>// @run-at       document-start<br>// ==/UserScript==<br><br>(function() {<br>    'use strict';<br>    const CONFIG = {<br>        bgDark: '#0d0d0d',<br>        bgCard: '#1a1a1a',<br>        bgHover: '#242424',<br>        bgInput: '#2a2a2a',<br>        textPrimary: '#e8e8e8',<br>        textSecondary: '#888888',<br>        textMuted: '#666666',<br>        borderColor: '#2a2a2a',<br>        accentColor: '#4a9eff',<br>        dangerColor: '#e88080',<br>    };<br>    function getColorBrightness(color) {<br>        if (!color || color === 'transparent' || color === 'rgba(0, 0, 0, 0)') return -1;<br>        const match = color.match(/\d+/g);<br>        if (!match) return -1;<br>        const r = parseInt(match[0]);<br>        const g = parseInt(match[1]);<br>        const b = parseInt(match[2]);<br>        return (r * 299 + g * 587 + b * 114) / 1000;<br>    }<br>    function isLightColor(color) {<br>        const brightness = getColorBrightness(color);<br>        return brightness > 160;<br>    }<br>    function getPageElements() {<br>        const allElements = document.querySelectorAll('*');<br>        const elements = [];<br>        const seen = new Set();<br>        allElements.forEach(el => {<br>            if (el.offsetWidth === 0 && el.offsetHeight === 0) return;<br>            const tag = el.tagName.toLowerCase();<br>            if (['video', 'img', 'svg', 'canvas', 'iframe', 'script', 'style'].includes(tag)) return;<br>            const classes = el.className ? String(el.className).split(/\s+/) : [];<br>            const id = el.id || '';<br>            let selector = '';<br>            if (id) {<br>                selector = `#${CSS.escape(id)}`;<br>            } else if (classes.length) {<br>                const validClasses = classes.filter(c => c && !c.startsWith('__') && !c.match(/^[0-9]/));<br>                if (validClasses.length) {<br>                    selector = '.' + validClasses.join('.');<br>                }<br>            }<br>            if (!selector || seen.has(selector)) return;<br>            seen.add(selector);<br>            const computed = window.getComputedStyle(el);<br>            const bg = computed.backgroundColor;<br>            const color = computed.color;<br>            const border = computed.borderColor;<br>            const isLightBg = bg && bg !== 'transparent' && bg !== 'rgba(0, 0, 0, 0)' && isLightColor(bg);<br>            const hasBorder = border && border !== 'rgba(0, 0, 0, 0)' && border !== 'transparent';<br>            if (isLightBg || hasBorder) {<br>                elements.push({<br>                    selector: selector,<br>                    tag: tag,<br>                    bg: bg,<br>                    color: color,<br>                    border: border,<br>                    isLightBg: isLightBg,<br>                    hasBorder: hasBorder,<br>                    bgBrightness: getColorBrightness(bg),<br>                });<br>            }<br>        });<br>        return elements;<br>    }<br>    function generateStyles(elements) {<br>        const { bgDark, bgCard, bgHover, bgInput, textPrimary, textSecondary, textMuted, borderColor, accentColor, dangerColor } = CONFIG;<br>        let css = `<br>            html, body, [id="m-app"], .m-layout, .m-layout-page,<br>            .container, .container--full, .m-home-page,<br>            .m-account-center__container {<br>                background-color: ${bgDark} !important;<br>                color: ${textPrimary} !important;<br>            }<br>        `;<br>        const processed = new Set();<br>        elements.forEach(el => {<br>            const selector = el.selector;<br>            if (processed.has(selector)) return;<br>            if (selector.includes('video') || selector.includes('img') || selector.includes('svg')) return;<br>            processed.add(selector);<br>            let styles = [];<br>            if (el.isLightBg) {<br>                const isCard = /card|item|list|post|feed|menu|panel|box|container|section|article/.test(selector);<br>                const isHeader = /header|nav|footer/.test(selector);<br>                const isButton = /button|btn/.test(selector);<br>                const isInput = /input|search|text/.test(selector);<br>                if (isCard) {<br>                    styles.push(`background-color: ${bgCard} !important`);<br>                } else if (isHeader) {<br>                    styles.push(`background-color: ${bgDark} !important`);<br>                } else if (isButton) {<br>                    styles.push(`background-color: ${bgHover} !important`);<br>                } else if (isInput) {<br>                    styles.push(`background-color: ${bgInput} !important`);<br>                } else {<br>                    styles.push(`background-color: transparent !important`);<br>                }<br>            }<br>            if (el.hasBorder) {<br>                styles.push(`border-color: ${borderColor} !important`);<br>            }<br>            if (el.color && isLightColor(el.color)) {<br>                const isSecondary = /secondary|sub|meta|time|count|info|desc/.test(selector);<br>                const isMuted = /muted|placeholder|hint/.test(selector);<br>                if (isSecondary) {<br>                    styles.push(`color: ${textSecondary} !important`);<br>                } else if (isMuted) {<br>                    styles.push(`color: ${textMuted} !important`);<br>                } else {<br>                    styles.push(`color: ${textPrimary} !important`);<br>                }<br>            }<br>            if (styles.length) {<br>                css += `\n    ${selector} {\n      ${styles.join(';\n      ')};\n    }`;<br>            }<br>        });<br>        css += `<br>            .m-header, .m-header__container, .m-header__container--mobile,<br>            .m-nav, .m-nav__container {<br>                background-color: ${bgCard} !important;<br>                border-bottom: 1px solid ${borderColor} !important;<br>            }<br>            .m-header__logo, .m-header__logo--mini {<br>                filter: brightness(0) invert(1) !important;<br>            }<br>            .m-input-search__placeholder {<br>                background-color: ${bgInput} !important;<br>                color: ${textSecondary} !important;<br>                border-color: ${borderColor} !important;<br>            }<br>            .m-nav__link { color: ${textSecondary} !important; }<br>            .m-nav__link--active { color: ${accentColor} !important; }<br>            .m-nav__link:hover { color: ${textPrimary} !important; }<br>            .m-post-card, .m-recommend__list__item {<br>                background-color: ${bgCard} !important;<br>                border: 1px solid ${borderColor} !important;<br>                border-radius: 12px !important;<br>            }<br>            .m-post-card:hover {<br>                background-color: ${bgHover} !important;<br>            }<br>            .m-post-card__title-text { color: ${textPrimary} !important; }<br>            .m-post-card__title-text:hover { color: ${accentColor} !important; }<br>            .m-post-card__info-text .text { color: ${textSecondary} !important; }<br>            .m-post-card__info-text .icon { color: ${textMuted} !important; }<br>            .m-post-card__duration {<br>                background: rgba(0,0,0,0.7) !important;<br>                color: #fff !important;<br>            }<br>            .m-home-page__posts h2, .m-home-page__hots h2 {<br>                color: ${textPrimary} !important;<br>                border-bottom: 2px solid ${borderColor} !important;<br>            }<br>            .m-home-page__hots {<br>                background-color: ${bgCard} !important;<br>                border: 1px solid ${borderColor} !important;<br>                border-radius: 12px !important;<br>            }<br>            .m-home-page__hots .m-post-card {<br>                background: transparent !important;<br>                border: none !important;<br>            }<br>            .m-home-page__hots .m-post-card:hover {<br>                background: ${bgHover} !important;<br>            }<br>            .m-home-swiper .title {<br>                background: rgba(0,0,0,0.6) !important;<br>                color: #fff !important;<br>            }<br>            .m-side-menu {<br>                background-color: ${bgCard} !important;<br>                border: 1px solid ${borderColor} !important;<br>                border-radius: 12px !important;<br>            }<br>            .m-menu__list {<br>                background-color: transparent !important;<br>                padding: 8px 0 !important;<br>                margin: 0 !important;<br>                list-style: none !important;<br>            }<br>            .m-menu__list li {<br>                background-color: transparent !important;<br>                margin: 2px 6px !important;<br>                border-radius: 8px !important;<br>                list-style: none !important;<br>            }<br>            .m-menu__list .item {<br>                display: flex !important;<br>                align-items: center !important;<br>                gap: 10px !important;<br>                padding: 10px 14px !important;<br>                color: ${textSecondary} !important;<br>                border-radius: 8px !important;<br>                text-decoration: none !important;<br>                transition: all 0.2s ease !important;<br>                font-size: 14px !important;<br>            }<br>            .m-menu__list .item .icon {<br>                color: ${textMuted} !important;<br>                font-size: 18px !important;<br>                flex-shrink: 0 !important;<br>                transition: color 0.2s !important;<br>            }<br>            .m-menu__list .item:hover {<br>                background-color: ${bgHover} !important;<br>                color: ${textPrimary} !important;<br>            }<br>            .m-menu__list .item:hover .icon {<br>                color: ${textPrimary} !important;<br>            }<br>            .m-menu__list .item.active {<br>                background-color: ${bgHover} !important;<br>                color: ${accentColor} !important;<br>            }<br>            .m-menu__list .item.active .icon {<br>                color: ${accentColor} !important;<br>            }<br>            .m-menu__list .item.danger {<br>                color: ${dangerColor} !important;<br>            }<br>            .m-menu__list .item.danger .icon {<br>                color: ${dangerColor} !important;<br>            }<br>            .m-menu__list .item.danger:hover {<br>                background-color: rgba(232, 128, 128, 0.12) !important;<br>                color: #ff6b6b !important;<br>            }<br>            .m-menu__list .item.danger:hover .icon {<br>                color: #ff6b6b !important;<br>            }<br>            .m-menu__list .item.disabled {<br>                color: #555 !important;<br>                cursor: not-allowed !important;<br>                opacity: 0.5 !important;<br>            }<br>            .m-menu__list .item.disabled .icon {<br>                color: #555 !important;<br>            }<br>            .m-menu__list .item.disabled:hover {<br>                background-color: transparent !important;<br>                color: #555 !important;<br>            }<br>            .m-menu__list .item.disabled:hover .icon {<br>                color: #555 !important;<br>            }<br>            .m-menu__list .n-badge {<br>                background-color: transparent !important;<br>            }<br>            .m-menu__list .n-badge .n-badge-sup {<br>                background-color: ${accentColor} !important;<br>                color: #fff !important;<br>            }<br>            .m-side-menu .n-scrollbar-rail__scrollbar {<br>                background-color: #444 !important;<br>            }<br>            .m-side-menu .n-scrollbar-rail__scrollbar:hover {<br>                background-color: #666 !important;<br>            }<br>            .m-account-center__header {<br>                background-color: ${bgCard} !important;<br>                border: 1px solid ${borderColor} !important;<br>                border-radius: 16px !important;<br>                overflow: hidden !important;<br>            }<br>            .m-account-center__header-banner {<br>                background-color: #161616 !important;<br>            }<br>            .m-account-center__header-banner_mask {<br>                background: linear-gradient(to bottom, transparent 50%, rgba(0,0,0,0.7) 100%) !important;<br>            }<br>            .m-account-center__header-banner_user-info .username {<br>                color: ${textPrimary} !important;<br>            }<br>            .m-account-center__header-banner_user-info-detail > div:last-child {<br>                color: ${textSecondary} !important;<br>            }<br>            .m-account-center__header-user {<br>                background-color: ${bgCard} !important;<br>                padding: 16px 20px !important;<br>            }<br>            .m-account-center__header-user-bio { color: #ccc !important; }<br>            .m-account-center__header-user hr {<br>                border-color: ${borderColor} !important;<br>                opacity: 1 !important;<br>            }<br>            .m-account-center__header-user-count { background-color: transparent !important; }<br>            .m-account-center__header-user-count > div { background-color: transparent !important; }<br>            .m-account-center__header-user-count .number { color: ${textPrimary} !important; }<br>            .m-account-center__header-user-count .label { color: ${textSecondary} !important; }<br>            .m-follow-button {<br>                background-color: ${accentColor} !important;<br>                color: #fff !important;<br>                border: none !important;<br>                border-radius: 20px !important;<br>            }<br>            .m-follow-button:hover { background-color: #6bb3ff !important; }<br>            .m-container.m-feed-list {<br>                background-color: ${bgCard} !important;<br>                border: 1px solid ${borderColor} !important;<br>                border-radius: 16px !important;<br>            }<br>            .m-load span { color: ${textSecondary} !important; }<br>            .m-account-center__side { background-color: transparent !important; }<br>            .m-account-center__side-section {<br>                background-color: ${bgCard} !important;<br>                border: 1px solid ${borderColor} !important;<br>                border-radius: 12px !important;<br>                padding: 16px !important;<br>            }<br>            .m-account-center__side-section h4 {<br>                color: ${textPrimary} !important;<br>                border-bottom: 1px solid ${borderColor} !important;<br>                padding-bottom: 8px !important;<br>            }<br>            .m-account-center__side-section h4 .icon { color: ${accentColor} !important; }<br>            .m-account-center__side-section .content a { color: ${textSecondary} !important; }<br>            .m-account-center__side-section .content a:hover { color: ${accentColor} !important; }<br>            .m-user-card {<br>                background-color: ${bgCard} !important;<br>                border: 1px solid ${borderColor} !important;<br>                border-radius: 16px !important;<br>                color: ${textPrimary} !important;<br>            }<br>            .m-user-card hr { border-color: ${borderColor} !important; opacity: 1 !important; }<br>            .m-user-card__premium .m-text:last-child {<br>                color: ${accentColor} !important;<br>                font-weight: 500 !important;<br>            }<br>            .m-user-card__fans .m-text.secondary { color: ${textSecondary} !important; }<br>            .m-user-card__action:hover { background-color: ${bgHover} !important; }<br>            .m-user-card .n-progress-graph-line-rail { background-color: ${bgInput} !important; }<br>            .m-user-card .n-progress-graph-line-fill { background-color: #a78bfa !important; }<br>            .m-footer, .m-footer__container {<br>                background-color: ${bgDark} !important;<br>                border-top: 1px solid ${borderColor} !important;<br>            }<br>            .m-footer__section .content a { color: ${textSecondary} !important; }<br>            .m-footer__section .content a:hover { color: ${accentColor} !important; }<br>            .m-footer__copyright { color: ${textMuted} !important; }<br>            .m-footer__copyright a { color: ${textSecondary} !important; }<br>            p, h1, h2, h3, h4, span, div, a, li, label {<br>                color: ${textPrimary} !important;<br>            }<br>            a:hover { color: ${accentColor} !important; }<br>            input, textarea, select {<br>                background-color: ${bgInput} !important;<br>                color: ${textPrimary} !important;<br>                border-color: ${borderColor} !important;<br>            }<br>            ::-webkit-scrollbar { width: 8px; height: 8px; }<br>            ::-webkit-scrollbar-track { background: ${bgCard}; }<br>            ::-webkit-scrollbar-thumb { background: #444; border-radius: 4px; }<br>            ::-webkit-scrollbar-thumb:hover { background: #555; }<br>            video, .video-player, [class*="player"] {<br>                background-color: #000 !important;<br>            }<br>            img, svg, canvas, .m-img {<br>                background-color: transparent !important;<br>            }<br>            .n-skeleton {<br>                --n-color-start: ${bgHover} !important;<br>                --n-color-end: ${bgInput} !important;<br>            }<br>            .m-badge .m-badge__item {<br>                background-color: transparent !important;<br>            }<br>            .m-account-center__header-banner_user .m-avatar {<br>                background-color: ${bgInput} !important;<br>                border: 3px solid #333 !important;<br>            }<br>            .m-side-qualification .m-text.secondary { color: ${textSecondary} !important; }<br>            .m-side-qualification .m-text.tertiary { color: ${textMuted} !important; }<br>            .m-side-qualification hr { border-color: ${borderColor} !important; opacity: 1 !important; }<br>            .m-side-qualification a { color: ${accentColor} !important; }<br>            .m-side-qualification .lic-img { filter: brightness(0.8) !important; }<br>        `;<br>        return css;<br>    }<br>    function applyDarkMode() {<br>        const oldStyle = document.getElementById('mfuns-dark-mode-auto');<br>        if (oldStyle) {<br>            oldStyle.remove();<br>        }<br>        const elements = getPageElements();<br>        console.log(`find ${elements.length} elements`);<br>        const css = generateStyles(elements);<br>        const style = document.createElement('style');<br>        style.id = 'mfuns-dark-mode-auto';<br>        style.textContent = css;<br>        document.head.appendChild(style);<br>        document.documentElement.classList.add('mfuns-dark-active');<br>        console.log('darkmode=1');<br>    }<br>    function debounce(func, wait) {<br>        let timeout;<br>        return function(...args) {<br>            clearTimeout(timeout);<br>            timeout = setTimeout(() => func.apply(this, args), wait);<br>        };<br>    }<br>    function init() {<br>        if (document.readyState === 'complete') {<br>            applyDarkMode();<br>        } else {<br>            document.addEventListener('DOMContentLoaded', applyDarkMode);<br>            window.addEventListener('load', applyDarkMode);<br>        }<br>        const debouncedApply = debounce(applyDarkMode, 300);<br>        document.addEventListener('click', function(e) {<br>            if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') {<br>                return;<br>            }<br>            if (e.target.tagName === 'VIDEO') {<br>                return;<br>            }<br>            debouncedApply();<br>        }, true);<br>        const observer = new MutationObserver(debounce(applyDarkMode, 500));<br>        observer.observe(document.body, {<br>            childList: true,<br>            subtree: true,<br>            attributes: true,<br>            attributeFilter: ['class', 'style'],<br>        });<br>        console.log('done');<br>    }<br>    if (document.readyState === 'loading') {<br>        document.addEventListener('DOMContentLoaded', init);<br>    } else {<br>        init();<br>    }<br>})();

当然肯定是不会那么简单的,因为站点原来是紫色色调,直接反转会有点不协调,所以我参照一个bilibili的美化插件改成了蓝色并且给很多需要突出的元素比如侧边栏,视频这些都做了一个圆角边框的处理,这样显着也挺好看的我感觉。

但是这还是有许多的bug毕竟有的css原来就是实时刷新的比如说投稿的时候那个自动保存,这就会带动其他元素一起刷新导致黑色失效,目前我也不怎么喜欢投稿所以这个问题我现在也懒得修,只能说等有时间了去针对这些特别的页面去专门做适配。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注