Jolly Blue Man

routine

Dark Mode Toggle

A modified version of the bootstrap darkmode toggle script. This one adds a .dark-mode or .light-mode class to the html element instead of a data attribute, and can swap out images that have a prefers-color-scheme alternative. Any element with the class .theme-toggle can be used to swap modes with a click.

Snippet:

<script>
    /*!
     * Color mode toggler for Bootstrap's docs (https://getbootstrap.com/)
     * Copyright 2011-2023 The Bootstrap Authors
     * Licensed under the Creative Commons Attribution 3.0 Unported License.
     * Modified by Tom Wilford 2024 to include:
     *  - Image Mode Switcher: https://michaelti.ca/sandbox/2020/05/01/dark-mode-images-with-a-manual-toggle-switch/
     */

    (() => {
        "use strict";

        const getStoredTheme = () => localStorage.getItem("theme");
        const setStoredTheme = (theme) => localStorage.setItem("theme", theme);

        const getPreferredTheme = () => {
            const storedTheme = getStoredTheme();
            if (storedTheme) {
                return storedTheme;
            }

            return window.matchMedia("(prefers-color-scheme: dark)").matches
                ? "dark"
                : "light";
        };

        const setTheme = (theme) => {
            if (
                theme === "auto" &&
                window.matchMedia("(prefers-color-scheme: dark)").matches
            ) {
                document.documentElement.classList.add("dark-mode");
                document.documentElement.classList.remove("light-mode");
            } else {
                const add = theme + '-mode';
                const remove = theme === 'dark' ? 'light-mode' : 'dark-mode';
                document.documentElement.classList.remove(remove);
                document.documentElement.classList.add(add);
            }
        };

        const setPictureTheme = (colorScheme) =>
        {
            document.querySelectorAll("picture > source[data-cloned-theme]").forEach((el) => {
                el.remove();
            });

            if (colorScheme) {
                document
                    .querySelectorAll(`picture > source[media*="(prefers-color-scheme: ${colorScheme})"]`)
                    .forEach((el) => {
                        const cloned = el.cloneNode();
                        cloned.removeAttribute("media");
                        cloned.setAttribute("data-cloned-theme", colorScheme);
                        el.parentNode.prepend(cloned);
                    });
            }
        };

        setTheme(getPreferredTheme());
        setPictureTheme(getPreferredTheme())

        window
            .matchMedia("(prefers-color-scheme: dark)")
            .addEventListener("change", () => {
                const storedTheme = getStoredTheme();
                if (storedTheme !== "light" && storedTheme !== "dark") {
                    setTheme(getPreferredTheme());
                    setPictureTheme(getPreferredTheme())
                }
            });

        window.addEventListener("DOMContentLoaded", () => {
            setPictureTheme(getPreferredTheme())

            document.querySelectorAll(".theme-toggle").forEach((toggle) => {
                toggle.addEventListener("click", () => {
                    const theme = getPreferredTheme() === 'dark' ? 'light' : 'dark';
                    setStoredTheme(theme);
                    setTheme(theme);
                    setPictureTheme(theme)
                });
            });
        });
    })();
</script>