Wikipedia Dark Theme

Script gives wikipedia pages a dark color theme

目前為 2019-06-23 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Wikipedia Dark Theme
// @namespace    https://github.com/MaxsLi/WikipediaDarkTheme
// @version      0.88
// @icon         https://www.wikipedia.org/favicon.ico
// @description  Script gives wikipedia pages a dark color theme
// @author       Shangru Li
// @match        *://*.wikipedia.org/*
// @grant        none
// @run-at       document-start
// @license      MIT
// @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", "nuvola_apps", "white_flag_icon",
            "wiki_letter_w_cropped", "edit-copy_purple-wikiq", "acap", "portal-puzzle",
            "star_of_life", "disambig-dark", "gnome", "office-book", "audio-input-microphone",
            "hsutvald2", "hspolitic", "hsdagensdatum", "hsvissteduatt", "pl_wiki_aktualnosci_ikona",
            "hsbild", "wiki_aktualnosci_ikona", "hs_vdq", "hssamarbete", "hswpedia", "w-circle",
            "red_pencil_icon", "merge-arrow", "generic_with_pencil", "hsaktuell", "hsearth",
            "wikimedia-logo-circle", "wiktionary_ko_without_text", "mediawiki-notext",
            "wiktprintable_without_text", "dialog-information", "applications-office",
            "celestia", "antistub"
        ];
        // list of tags of images to have color inverted
        var invert_src_tag = [
            "loudspeaker", "signature", "signatur", "chinese_characters", "/media/math/render/",
            "translation_to_english_arrow", "disambig_gray", "wikimedia-logo_black", "blue_pencil"
        ];
        // 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 + "%)";
    }
})();