Greasy Fork

Greasy Fork is available in English.

维基百科纯黑主题

Pure Dark theme for Wikipedia pages

当前为 2019-05-12 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Wikipedia Dark Theme
// @namespace    https://github.com/MaxsLi/WikipediaDarkTheme
// @version      0.86
// @icon         https://www.wikipedia.org/favicon.ico
// @description  Pure Dark theme for Wikipedia pages
// @author       Shangru Li
// @match        *://*.wikipedia.org/*
// @grant        none
// @run-at       document-start
// @name:zh-CN          维基百科纯黑主题
// @description:zh-cn   给予维基百科网页一个黑色主题
// ==/UserScript==

// The main function is called at every `onreadystatechange`
// Document state will go from `loading` --> `interactive` --> `complete`
// Metadata Block `@run-at document-start` will ensure the script start executing when `loading`
(document.onreadystatechange = function () {

    //##################---default_values---#####################################
    var default_contrastValue = 8;
    var default_foregroundColor = "rgb(238, 255, 255)";
    var default_backgroundColor = "rgb(35, 35, 35)";
    //##################---one_could_alter_if_desired---#########################

    //##################---controller---#########################################

    // Check each document state and take action accordingly
    if ('loading' == document.readyState) {
        // if document is loading we first hide the whole page
        setPageVisibility("hidden");
    } else if ('interactive' == document.readyState) {
        // when document has finished loading and the document has been parsed, we can start changing the styles
        setPage();
    } else if ('complete' == document.readyState) {
        // after the document is finished we can set the whole page back to `visible`
        setPageVisibility("visible")
    }

    //##################---main function---######################################

    // function to change the all the elements on a page to desired color
    function setPage() {
        'use strict';
        // General idea is to put all elements on a wikipedia page to an array `allElements`
        // traverse through this array and reverse the color of each element accordingly
        // running time o(n), where n is the number of elements on a page

        // get all elements in the document, use to be `getElementsByTagName('*')`
        // However `querySelectorAll()` seems to be faster
        var allElements = document.querySelectorAll('*');
        // loop over all elements on the page
        for (var i = 0; i < allElements.length; i++) {
            var currentElement = allElements[i];
            // exception handler
            try {
                // check for images
                if (currentElement.nodeName.toLowerCase() == 'img') {
                    // check current image
                    checkImage(currentElement);
                    continue;
                }
                // check for wiki logo
                else if (currentElement.className.toLowerCase().includes("mw-wiki-logo")) {
                    invertImage(currentElement, 90);
                    continue;
                }
                // check for keyboard-key
                else if (currentElement.className.toLowerCase().includes('keyboard-key')) {
                    currentElement.style.foregroundColor = default_backgroundColor;
                    continue;
                }
                // check for legends and pie charts
                else if (currentElement.className.toLowerCase().includes('legend') ||
                    currentElement.className.toLowerCase().includes('border') ||
                    currentElement.style.borderColor.toLowerCase().includes('transparent')) {
                    continue;
                }
                // get the foreground color of the `currentElement`, using `getComputedStyle` will return the actual showing
                // color of the given element in `rgb` format. However, this method seems to need the document first be loaded
                // after returning the computed style. Hence, to get around, we hide the entire page while waiting the document
                // to load, then un-hide after finished.
                var foregroundColor = window.getComputedStyle(currentElement, null).getPropertyValue("color");
                var backgroundColor = window.getComputedStyle(currentElement, null).getPropertyValue("background-color");
                // temporary helper variables
                var r, g, b;
                // split the `default_backgroundColor` string to array of RGB values.
                var default_backgroundColor_array = default_backgroundColor.match(/rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)/);

                // check if the `foregroundColor` is of type RGB value.
                if (foregroundColor.match(/rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)/)) {
                    // split color to RGB values.
                    foregroundColor = foregroundColor.match(/rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)/);
                    // inverse color
                    r = 255 - foregroundColor[1];
                    g = 255 - foregroundColor[2];
                    b = 255 - foregroundColor[3];
                    // checking contrast between foregroundColor of `currentElement` and the `default_backgroundColor`
                    // make sure the contrast is high enough to ensure a decent viewing experience.
                    while (contrast([r, g, b], [default_backgroundColor_array[1], default_backgroundColor_array[2], default_backgroundColor_array[3]]) <
                        default_contrastValue) {
                        // increase each value by 30 each loop, i.e., increase the brightness of the current color
                        r = r + 30;
                        g = g + 30;
                        b = b + 30;
                    }
                    // set color
                    currentElement.style.color = 'rgb(' + r + ', ' + g + ', ' + b + ')';
                } else {
                    // set default color
                    currentElement.style.color = default_foregroundColor;
                }

                // check if the `backgroundColor` is of type RGB value.
                if (backgroundColor.match(/rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)/)) {
                    // split color to RGB values.
                    backgroundColor = backgroundColor.match(/rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)/);
                    // inverse color
                    r = 255 - backgroundColor[1];
                    g = 255 - backgroundColor[2];
                    b = 255 - backgroundColor[3];
                    // for the background color one would not check for contrast, instead we increase the brightness of `backgroundColor`
                    // if it is too dark, providing a comforting reading experience.
                    if (r < default_backgroundColor_array[1] - 10 && g < default_backgroundColor_array[2] - 10 && g < default_backgroundColor_array[3] - 10) {
                        r = r + 30;
                        g = g + 30;
                        b = b + 30;
                    }
                    // if the background is too bright, we decrease the brightness of `backgroundColor`
                    while (contrast([r, g, b], [default_backgroundColor_array[1], default_backgroundColor_array[2], default_backgroundColor_array[3]]) >
                        default_contrastValue) {
                        r = r - 30;
                        g = g - 30;
                        b = b - 30;
                    }
                    // set color
                    currentElement.style.backgroundColor = 'rgb(' + r + ', ' + g + ', ' + b + ')';
                } else {
                    // set default color
                    currentElement.style.backgroundColor = default_backgroundColor;
                }
            } catch (e) {
                // print any exception messages
                console.log(e);
            }
        }
    }

    //##################---helper functions---###################################

    // helper function to set the visibility of a html page
    function setPageVisibility(visibility) {
        // get the entire html page
        var EntirePage = document.getElementsByTagName('html')[0];
        // set the page's background color to `default_backgroundColor`
        EntirePage.style.backgroundColor = default_backgroundColor;
        EntirePage.style.visibility = visibility;
    }

    // helper function to calculate the luminance of given `r`, `g`, `b` value
    function luminance(r, g, b) {
        var a = [r, g, b].map(function (v) {
            v /= 255;
            return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
        });
        return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
    }

    // helper function to calculate the contrast of two given color `rgb1` and `rgb2`
    function contrast(rgb1, rgb2) {
        return (luminance(rgb1[0], rgb1[1], rgb1[2]) + 0.05) / (luminance(rgb2[0], rgb2[1], rgb2[2]) + 0.05);
    }

    // helper function to check given source link has contain any substring specified in the list
    function checkImage(element) {
        // list of tags of the wikipedia logos and symbols to be excluded from setting backgroundColor to white
        // list is subject to amend
        var exclude_src_tag = [
            "protection-shackle", "green_check", "symbol_support_vote",
            "edit-clear", "information_icon", "increase2", "decrease_positive",
            "steady2", "decrease2", "increase_negative", "red_question_mark",
            "blue_check", "x_mark", "yes_check", "twemoji", "walnut", "cscr-featured",
            "sound-openclipart", "folder_hexagonal_icon", "symbol_book_class2",
            "question_book-new", "wiktionary-logo", "commons-logo", "wikinews-logo",
            "wikiquote-logo", "wikivoyage-logo", "sound-icon", "wikibooks-logo",
            "wikiversity-logo", "ambox", "system-search", "split-arrows", "wikiversity_logo",
            "wikisource-logo", "wikimedia_community_logo", "wikidata-logo", "mediawiki-logo",
            "wikispecies-logo", "blue_pencil", "nuvola_apps", "white_flag_icon",
            "wiki_letter_w_cropped", "edit-copy_purple-wikiq", "acap", "portal-puzzle",
            "star_of_life", "disambig-dark", "gnome", "office-book"
        ];
        // list of tags of images to have color inverted
        var invert_src_tag = [
            "loudspeaker", "signature", "signatur", "chinese_characters", "/media/math/render/"
        ];
        // loop over the `exclude_src_tag` list to set background color
        for (var i = 0; i < exclude_src_tag.length; i++) {
            if (element.src.toLowerCase().includes(exclude_src_tag[i])) {
                // element is in the list, stop looping
                break;
            }
        }
        // i equals list's length implies the element is not in the list
        if (i == exclude_src_tag.length) {
            // if element is not in the list, set the background color to white
            element.style.backgroundColor = "rgb(255, 255, 255)";
        }

        // loop over the `invert_src_tag` list to invert image color
        for (var j = 0; j < invert_src_tag.length; j++) {
            if (element.src.toLowerCase().includes(invert_src_tag[j])) {
                // invert image if element is in the list
                // invert 86 per cent in order to match the background color
                invertImage(element, 86);
                break;
            }
        }
    }

    // helper function to invert a image to desired percentage
    function invertImage(img, percent) {
        // invert images by adding a filter tag
        img.style.filter = "invert(" + percent + "%)";
    }
})();