简体   繁体   English

如何覆盖 css 首选配色方案设置

[英]How to override css prefers-color-scheme setting

I am implementing a dark mode, as macOS, Windows and iOS have all introduced dark modes.我正在实现深色模式,因为 macOS、Windows 和 iOS 都引入了深色模式。

There is a native option for Safari, Chrome, and Firefox, using the following CSS media rule: Safari、Chrome 和 Firefox 有一个本机选项,使用以下 CSS 媒体规则:

@media (prefers-color-scheme: dark) {
body {
    color:#fff;
    background:#333333
}

This will automatically identify systems that are set to dark modes, and apply the enclosed CSS rules.这将自动识别设置为黑暗模式的系统,并应用随附的 CSS 规则。

However;然而; even though users may have their system set to dark mode, it may be the case that they prefer the light or default theme of a specific website.即使用户可能将他们的系统设置为深色模式,他们也可能更喜欢特定网站的浅色或默认主题。 There is also the case of Microsoft Edge users which does not (yet) support @media (prefers-color-scheme . For the best user experience, I want to ensure that these users can toggle between dark and default modes for those cases.还有一些 Microsoft Edge 用户(还)不支持@media (prefers-color-scheme )。为了获得最佳用户体验,我想确保这些用户可以在这些情况下在暗模式和默认模式之间切换。

Is there a method that this can be performed, possibly with HTML 5 or JavaScript?有没有一种方法可以执行此操作,可能使用 HTML 5 或 JavaScript? I'd include the code I have tried, but I haven't been able to find any information on implementing this whatsoever!我会包含我尝试过的代码,但我无法找到任何关于实现它的信息!

I have determined an appropriate solution, it is as follows:我已经确定了一个合适的解决方案,如下:

CSS will use variables and themes: CSS 将使用变量和主题:

// root/default variables
:root {
    --font-color: #000;
    --link-color:#1C75B9;
    --link-white-color:#fff;
    --bg-color: rgb(243,243,243);
}
//dark theme
[data-theme="dark"] {
    --font-color: #c1bfbd;
    --link-color:#0a86da;
    --link-white-color:#c1bfbd;
    --bg-color: #333;
}

The variables are then called where necessary, for example:然后在必要时调用变量,例如:

//the redundancy is for backwards compatibility with browsers that do not support CSS variables.
body
{
    color:#000;
    color:var(--font-color);
    background:rgb(243,243,243);
    background:var(--bg-color);
}

JavaScript is used to identify which theme the user has set, or if they have over-ridden their OS theme, as well as to toggle between the two, this is included in the header prior to the output of the html <body>...</body> : JavaScript 用于识别用户设置了哪个主题,或者他们是否覆盖了他们的操作系统主题,以及在两者之间切换,这包含在 html <body>...</body>

//determines if the user has a set theme
function detectColorScheme(){
    var theme="light";    //default to light

    //local storage is used to override OS theme settings
    if(localStorage.getItem("theme")){
        if(localStorage.getItem("theme") == "dark"){
            var theme = "dark";
        }
    } else if(!window.matchMedia) {
        //matchMedia method not supported
        return false;
    } else if(window.matchMedia("(prefers-color-scheme: dark)").matches) {
        //OS theme setting detected as dark
        var theme = "dark";
    }

    //dark theme preferred, set document with a `data-theme` attribute
    if (theme=="dark") {
         document.documentElement.setAttribute("data-theme", "dark");
    }
}
detectColorScheme();

This javascript is used to toggle between the settings, it does not need to be included in the header of the page, but can be included wherever此javascript用于在设置之间切换,不需要包含在页面标题中,但可以包含在任何地方

//identify the toggle switch HTML element
const toggleSwitch = document.querySelector('#theme-switch input[type="checkbox"]');

//function that changes the theme, and sets a localStorage variable to track the theme between page loads
function switchTheme(e) {
    if (e.target.checked) {
        localStorage.setItem('theme', 'dark');
        document.documentElement.setAttribute('data-theme', 'dark');
        toggleSwitch.checked = true;
    } else {
        localStorage.setItem('theme', 'light');
        document.documentElement.setAttribute('data-theme', 'light');
        toggleSwitch.checked = false;
    }    
}

//listener for changing themes
toggleSwitch.addEventListener('change', switchTheme, false);

//pre-check the dark-theme checkbox if dark-theme is set
if (document.documentElement.getAttribute("data-theme") == "dark"){
    toggleSwitch.checked = true;
}

finally, the HTML checkbox to toggle between themes:最后,在主题之间切换的 HTML 复选框:

<label id="theme-switch" class="theme-switch" for="checkbox_theme">
    <input type="checkbox" id="checkbox_theme">
</label>

Through the use of CSS variables and JavaScript, we can automatically determine the users theme, apply it, and allow the user to over-ride it as well.通过使用 CSS 变量和 JavaScript,我们可以自动确定用户主题,应用它,并允许用户覆盖它。 [As of the current time of writing this (2019/06/10), only Firefox and Safari support the automatic theme detection] [截至目前(2019/06/10),只有 Firefox 和 Safari 支持自动主题检测]

You can use my custom element <dark-mode-toggle> that initially adheres to the user's prefers-color-scheme setting, but that also allows the user to (permanently or temporarily) override it.您可以使用我的自定义元素<dark-mode-toggle>最初遵循用户的prefers-color-scheme设置,但也允许用户(永久或临时)覆盖它。 The toggle works both with separate CSS files or with classes that are toggled.切换既适用于单独的 CSS 文件,也适用于切换的类。 The README has examples for both approaches. 自述文件包含两种方法的示例。

Not sure, why all answers are so complicated.不确定,为什么所有答案都如此复杂。

Use CSS variables, set a default value, and an opposite value in a media query, as usual.像往常一样,在媒体查询中使用 CSS 变量、设置默认值和相反的值。 Also set the values in two classes.还要在两个类中设置值。 Implement a toggle that toggles these classes when clicked.实现一个在单击时切换这些类的切换。

By default, automatic light/dark mode is used based on the system color scheme.默认情况下,根据系统配色方案使用自动明暗模式。 Using the toggle switches to manual light/dark mode.使用拨动开关切换到手动亮/暗模式。 It returns to automatic light/dark mode after refreshing the page (or removing the class from the html element).刷新页面(或从 html 元素中删除类)后,它会返回自动亮/暗模式。

 // toggle to switch classes between .light and .dark // if no class is present (initial state), then assume current state based on system color scheme // if system color scheme is not supported, then assume current state is light function toggleDarkMode() { if (document.documentElement.classList.contains("light")) { document.documentElement.classList.remove("light") document.documentElement.classList.add("dark") } else if (document.documentElement.classList.contains("dark")) { document.documentElement.classList.remove("dark") document.documentElement.classList.add("light") } else { if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { document.documentElement.classList.add("dark") } else { document.documentElement.classList.add("light") } } }
 /* automatic/manual light mode */ :root, :root.light { --some-value: black; --some-other-value: white; } /* automatic dark mode */ /* ❗️ keep the rules in sync with the manual dark mode below! */ @media (prefers-color-scheme: dark) { :root { --some-value: white; --some-other-value: black; } } /* manual dark mode /* ❗️ keep the rules in sync with the automatic dark mode above! */ :root.dark { --some-value: white; --some-other-value: black; } /* use the variables */ body { color: var(--some-value); background-color: var(--some-other-value); }
 <button onClick="toggleDarkMode()">Toggle</button> <h1>Hello world!</h1>

Here's an answer that respects the default prefers-color-scheme , and only then lets you toggle via localStorage .这是一个尊重默认值prefers-color-scheme的答案,然后才允许您通过localStorage切换。 This spares the second it takes to figure out the default scheme via JS plus people will use the default scheme even without JS.这节省了通过 JS 找出默认方案所需的时间,而且即使没有 JS,人们也会使用默认方案。

I don't like having to declare a default style (I went for Dark) and then re-declaring it as a class called dark-mode but it's unavoidable .我不喜欢必须声明一个默认样式(我选择了 Dark),然后将其重新声明为一个名为dark-mode的类,但这是不可避免的。

Note this forum seems to block localStorage so you have to try the code somewhere else.请注意,此论坛似乎阻止了localStorage ,因此您必须在其他地方尝试代码。

 var theme, prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)"); if (prefersDarkScheme.matches) theme = document.body.classList.contains("light-mode") ? "light" : "dark"; else theme = document.body.classList.contains("dark-mode") ? "dark" : "light"; localStorage.setItem("theme", theme); function toggle() { var currentTheme = localStorage.getItem("theme"); if (currentTheme == "dark") document.body.classList.toggle("light-mode"); else if (currentTheme == "light") document.body.classList.toggle("dark-mode"); }
 .dark-mode {color: white; background-color: black} .dark-mode a:link {color: DeepSkyBlue} .light-mode {color: black; background-color: white} .light-mode a:link {color: green} @media (prefers-color-scheme: dark) { body {color: white; background-color: black} a:link {color: DeepSkyBlue} }
 <button onclick="toggle()">Toggle Light/Dark Mode</button>

Took the solution provided by @JimmyBanks and 1) turned the checkbox into a toggling text button, and 2) added automatic theme switching on OS theme change.采用@JimmyBanks 提供的解决方案,1)将复选框变成一个切换文本按钮,2)在操作系统主题更改时添加自动主题切换。

CSS is unchanged, with light themes stored in the :root and dark themes stored under [data-theme="dark"] : CSS 没有改变,浅色主题存储在:root中,深色主题存储在[data-theme="dark"]下:

:root {
  --color_01: #000;
  --color_02: #fff;
  --color_03: #888;
}

[data-theme="dark"] {
  --color_01: #fff;
  --color_02: #000;
  --color_03: #777;
}

The <head> JS has some edits, including a few omissions and the move of the data-theme statement to the subsequent JS block: <head> JS 有一些修改,包括一些遗漏以及将data-theme语句移至后续 JS 块:

var theme = 'light';
if (localStorage.getItem('theme')) {
  if (localStorage.getItem('theme') === 'dark') {
    theme = 'dark';
  }
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
  theme = 'dark';
}

And here is the edit to the second block of JS, plus related HTML.这是对第二个 JS 块的编辑,以及相关的 HTML。 theme_switch toggles the theme, while theme_OS automatically updates the site's theme with changes to the OS theme. theme_switch切换主题,而theme_OS自动更新站点主题并更改 OS 主题。

var theme;
function theme_apply() {
  'use strict';
  if (theme === 'light') {
    document.getElementById('theme_readout').innerHTML = 'Dark';
    document.documentElement.setAttribute('data-theme', 'light');
    localStorage.setItem('theme', 'light');
  } else {
    document.getElementById('theme_readout').innerHTML = 'Light';
    document.documentElement.setAttribute('data-theme', 'dark');
    localStorage.setItem('theme', 'dark');
  }
}
theme_apply();
function theme_switch() {
  'use strict';
  if (theme === 'light') {
    theme = 'dark';
  } else {
    theme = 'light';
  }
  theme_apply();
}
var theme_OS = window.matchMedia('(prefers-color-scheme: light)');
theme_OS.addEventListener('change', function (e) {
  'use strict';
  if (e.matches) {
    theme = 'light';
  } else {
    theme = 'dark';
  }
  theme_apply();
});
<a onclick="theme_switch()">Theme: <span id="theme_readout"></span></a>

Please let me know if you have any suggestions for improvement!如果您有任何改进建议,请告诉我!

None of the above suited me.以上都不适合我。 I decided to approach the problem from a different perspective.我决定从不同的角度来处理这个问题。 Year is 2021.年份是 2021 年。


The following offers:以下优惠:

  • System preferences respect.系统偏好方面。
  • System preferences overwrite.系统首选项覆盖。
  • Scrollbars color scheme respect.滚动条配色方案方面。
  • Universal browser support.通用浏览器支持。 (IE end of life, August 17th 2021 🥳✌️🎉) (IE 生命周期结束,2021 年 8 月 17 日🥳✌️🎉)

When you take a look at the MDN Web Docs page for prefers-color-scheme you can read the following:当您查看MDN Web Docs 页面以获取prefers-color-scheme时,您可以阅读以下内容:

The prefers-color-scheme CSS media feature is used to detect if the user has requested a light or dark color theme. prefers-color-scheme CSS 媒体功能用于检测用户是否请求了浅色或深色主题。 [...] [...]

light Indicates that user has notified that they prefer an interface that has a light theme, or has not expressed an active preference . light表示用户已通知他们更喜欢具有浅色主题的界面,或者尚未表达积极的偏好

So for any browsers, by default, the prefers-color-scheme is either set to light or isn't supported.因此,对于任何浏览器,默认情况下, prefers-color-scheme要么设置为light ,要么不受支持。

One of the problem I had with the accepted answer was that the changes were not affecting the scrollbar color.我在接受答案时遇到的问题之一是更改不会影响滚动条颜色。 This can be handle using the color-scheme CSS property coupled to the :root pseudo element.这可以使用与:root伪元素耦合的color-scheme CSS 属性来处理。

The other problem I had was that, If a user was to change the system settings to light or dark, the website wouldn't be affeted by it and would generate miss-matches between both styles.我遇到的另一个问题是,如果用户将系统设置更改为亮或暗,则网站不会受到它的影响,并且会在两种样式之间产生不匹配。 We can fix that behaviour by coupling window.matchMedia( '(prefers-color-scheme: light)' ) to a onchange event listener.我们可以通过将window.matchMedia( '(prefers-color-scheme: light)' )耦合到onchange事件侦听器来修复该行为。

Here is the final script.这是最终的脚本。

(() => {
    var e = document.getElementById("tglScheme");
    window.matchMedia("(prefers-color-scheme: dark)").matches
        ? (document.head.insertAdjacentHTML("beforeend", '<style id="scheme">:root{color-scheme:dark}</style>'),
          document.body.classList.add("dark"),
          e && (e.checked = !0),
          window.localStorage.getItem("scheme") &&
              (document.getElementById("scheme").remove(), document.head.insertAdjacentHTML("beforeend", '<style id="scheme">:root{color-scheme:light}</style>'), document.body.classList.remove("dark"), e && (e.checked = !1)),
          e &&
              e.addEventListener("click", () => {
                  e.checked
                      ? (document.getElementById("scheme").remove(),
                        document.head.insertAdjacentHTML("beforeend", '<style id="scheme">:root{color-scheme:dark}</style>'),
                        document.body.classList.add("dark"),
                        localStorage.removeItem("scheme"))
                      : (document.getElementById("scheme").remove(),
                        document.head.insertAdjacentHTML("beforeend", '<style id="scheme">:root{color-scheme:light}</style>'),
                        document.body.classList.remove("dark"),
                        localStorage.setItem("scheme", 1));
              }))
        : (document.head.insertAdjacentHTML("beforeend", '<style id="scheme">:root{color-scheme:light}</style>'),
          e && (e.checked = !1),
          window.localStorage.getItem("scheme") &&
              (document.getElementById("scheme").remove(), document.head.insertAdjacentHTML("beforeend", '<style id="scheme">:root{color-scheme:dark}</style>'), document.body.classList.add("dark"), e && (e.checked = !0)),
          e &&
              e.addEventListener("click", () => {
                  e.checked
                      ? (document.getElementById("scheme").remove(),
                        document.head.insertAdjacentHTML("beforeend", '<style id="scheme">:root{color-scheme:dark}</style>'),
                        document.body.classList.add("dark"),
                        localStorage.setItem("scheme", 1))
                      : (document.getElementById("scheme").remove(),
                        document.head.insertAdjacentHTML("beforeend", '<style id="scheme">:root{color-scheme:light}</style>'),
                        document.body.classList.remove("dark"),
                        localStorage.removeItem("scheme"));
              }));
})(),
window.matchMedia("(prefers-color-scheme: light)").addEventListener("change", () => {
    location.reload(), localStorage.removeItem("scheme");
});

For the CSS side, we use the default variable custom property values fallback with the dark color in first position.对于 CSS 方面,我们使用默认变量自定义属性值回退,并将深色放在首位。 We can define all the necessary dark colors via the :root element.我们可以通过 :root 元素定义所有必要的深色。

:root body.dark {
  --app-bg-dark: #131313;
  --app-tx-dark: #f8f9fa;
}
body{
  background-color: var( --app-bg-dark, white );
  color: var( --app-tx-dark, black );
}
/* if dark mode isn't set, fall back to light. */

And for the html, a simple checkbox <input id="tglScheme" type="checkbox"> .对于 html,一个简单的复选框<input id="tglScheme" type="checkbox">

Finally here is the Codepen https://codepen.io/amarinediary/full/yLgppWW .最后是 Codepen https://codepen.io/amarinediary/full/yLgppWW

⚠️️ Codepen overwrites location.reload() so you won't be abble to test the live update on system change. ⚠️️ Codepen 会覆盖location.reload() ,因此您将无法测试系统更改的实时更新。 Don't hesitate to try it on your localhost.不要犹豫,在您的本地主机上尝试它。

TL;DR TL;博士


index.html

<!DOCTYPE html>
<html>
    <head>
        <meta name="color-scheme" content="light dark">
        <link rel="stylesheet" type="text/css" href="style.css" />
    </head>
    <body>
        <h1>Hello world</h1>
        <button id="toggle">Toggle</button>
        <script type="text/javascript" src="script.js"></script>
    </body>
</html>

style.css

.dark-mode {
    background-color: black;
    color: white;
}

.light-mode {
    background-color: white;
    color: black;
}

@media (prefers-color-scheme: dark) {
    body {
        background-color: black;
        color: white;
    }
}

script.js

/**
 * Adopt:
 * the theme from the system preferences; or
 * the previously stored mode from the `localStorage`
 */
var initialMode = "light";
var prefersColorSchemeDark = window.matchMedia( "(prefers-color-scheme: dark)" );

if ( prefersColorSchemeDark.matches ) {
    initialMode = "dark";
}

if( localStorage.getItem("initialMode") == null ) {
    localStorage.setItem("initialMode", initialMode);
}

if( localStorage.getItem("currentMode") == null ) {
    localStorage.setItem("currentMode", initialMode);
} else {
    let currentMode = localStorage.getItem("currentMode");
    if ( currentMode == "dark" && currentMode != initialMode ) {
        document.body.classList.add("dark-mode");
    } else if ( currentMode == "light" && currentMode != initialMode ) {
        document.body.classList.add("light-mode");
    }
}

/**
 * Process the toggle then store to `localStorage`
 */
document.getElementById('toggle').addEventListener("click", function() {
    var initialMode = localStorage.getItem("initialMode");
    let currentMode = localStorage.getItem("currentMode");

    if ( currentMode == "dark" && currentMode == initialMode ) {
        document.body.classList.add("light-mode");
        localStorage.setItem("currentMode", "light");
    } else if ( currentMode == "light" && currentMode == initialMode ) {
        document.body.classList.add("dark-mode");
        localStorage.setItem("currentMode", "dark");
    } else if ( currentMode != initialMode ) {
        document.body.removeAttribute("class");
        if( currentMode == "dark" ) {
            localStorage.setItem("currentMode", "light");
        } else {
            localStorage.setItem("currentMode", "dark");
        }
    }
},
false);

Details细节

This solution assumes that:该解决方案假定:

  1. Whatever was set on the system preferences (dark/light mode), that will be acknowledged as the initial mode无论在系统偏好设置(暗/亮模式)上设置什么,都将被确认为初始模式
  2. From the initial mode, the end-user then can toggle manually either dark mode or light mode从初始模式开始,最终用户可以手动切换暗模式或亮模式
  3. If the system does not have a dark mode feature, the light mode theme will be used如果系统没有深色模式功能,将使用浅色模式主题
  4. Whatever the theme (dark/light mode) that the end-user manually set previously, that will be the new initial mode on the next page reload/refresh无论最终用户之前手动设置的主题(暗/亮模式)如何,都将是下一页重新加载/刷新时的新初始模式

An alternative solution I found using the blog mybyways which is not mentioned anywhere else but works for me.我使用博客 mybyways 找到的替代解决方案在其他任何地方都没有提及,但对我有用。 This is useful only when html uses the prefers-color-scheme css media classes.这仅在 html 使用 prefers-color-scheme css 媒体类时才有用。

Unlike other answers, it uses the stylesheets' rules to append the class (as opposed to adding or removing "dark" or "light" from classList)与其他答案不同,它使用样式表的规则来附加类(而不是从 classList 中添加或删除“dark”或“light”)

By default it takes the style of OS setting and overrides it when toggled.默认情况下,它采用操作系统设置的样式并在切换时覆盖它。 I tried from Google Chrome labs but it didn't work out for me.我从 Google Chrome 实验室尝试过,但对我没有用。

 function setPreferredColorScheme(mode = "dark") { console.log("changing") for (var i = document.styleSheets[0].rules.length - 1; i >= 0; i--) { rule = document.styleSheets[0].rules[i].media; if (rule.mediaText.includes("prefers-color-scheme")) { console.log("includes color scheme") switch (mode) { case "light": console.log("light") rule.appendMedium("original-prefers-color-scheme"); if (rule.mediaText.includes("light")) rule.deleteMedium("(prefers-color-scheme: light)"); if (rule.mediaText.includes("dark")) rule.deleteMedium("(prefers-color-scheme: dark)"); break; case "dark": console.log("dark") rule.appendMedium("(prefers-color-scheme: light)"); rule.appendMedium("(prefers-color-scheme: dark)"); if (rule.mediaText.includes("original")) rule.deleteMedium("original-prefers-color-scheme"); break; default: console.log("default") rule.appendMedium("(prefers-color-scheme: dark)"); if (rule.mediaText.includes("light")) rule.deleteMedium("(prefers-color-scheme: light)"); if (rule.mediaText.includes("original")) rule.deleteMedium("original-prefers-color-scheme"); } break; } } }
 @media (prefers-color-scheme: light) { :root { color: pink; background-color: yellow; } } @media (prefers-color-scheme: dark) { :root { color: red; background-color: blue; } }
 <body> <button onClick="setPreferredColorScheme()"> toggle </button> </body>

Above is a working example ^以上是一个工作示例^

Full source: https://mybyways.com完整来源: https ://mybyways.com

I would create a second CSS file for a dark theme and use 我将为深色主题创建第二个CSS文件并使用

@media (prefers-color-scheme: dark) {
     @import url("dark.css");
}

And then, something along these lines to add it, using JavaScript, if the user selects dark mode. 然后,如果用户选择了暗模式,则可以使用JavaScript按照以下方式添加它。 If you give the link element an ID (or a global scope), you can remove it with JS, too. 如果为link元素提供ID(或全局范围),则也可以使用JS删除它。

My Solution (3 options in radio inputs: dark, system, light) adaptation of JimmyBanks and Meanderbilt Solution:我的解决方案(无线电输入中的 3 个选项:暗、系统、亮)改编自 JimmyBanks 和 Meanderbilt 解决方案:

its a bit verbose I guess, but I struggled a bit to wrap my head around it我想它有点冗长,但我有点挣扎才能把头绕过去

const themeSwitches = document.querySelectorAll('[data-color-theme-toggle]')

function removeColorThemeLocalStorage() {
  localStorage.removeItem('color-theme')
}

function saveColorTheme(colorTheme) {
  if (colorTheme === 'system') {
    removeColorThemeLocalStorage()
    return
  }
  localStorage.setItem('color-theme', colorTheme)
}

function applyColorTheme() {
  const localStorageColorTheme = localStorage.getItem('color-theme')
  const colorTheme = localStorageColorTheme || null
  if (colorTheme) {
    document.documentElement.setAttribute('data-color-theme', colorTheme)
  }
}

function themeSwitchHandler() {
  themeSwitches.forEach(themeSwitch => {
    const el = themeSwitch
    if (el.value === localStorage.getItem('color-theme')) {
      el.checked = true
    }

    el.addEventListener('change', () => {
      if (el.value !== 'system') {
        saveColorTheme(el.value)
        applyColorTheme(el.value)
      } else {
        removeColorThemeLocalStorage()
        document.documentElement.removeAttribute('data-color-theme')
      }
    })
  })
  applyColorTheme()
}
document.addEventListener('DOMContentLoaded', () => {
  themeSwitchHandler()
  applyColorTheme()
})

html {
  --hue-main: 220;
  --color-text: hsl(var(--hue-main), 10%, 25%);
  --color-text--high-contrast: hsl(var(--hue-main), 10%, 5%);
  --color-link: hsl(var(--hue-main), 40%, 30%);
  --color-background: hsl(var(--hue-main), 51%, 98.5%);
}

@media (prefers-color-scheme: dark) {
  html.no-js {
    --color-text: hsl(var(--hue-main), 5%, 60%);
    --color-text--high-contrast: hsl(var(--hue-main), 10%, 80%);
    --color-link: hsl(var(--hue-main), 60%, 60%);
    --color-background: hsl(var(--hue-main), 10%, 12.5%);
  }
}

[data-color-theme='dark'] {
  --color-text: hsl(var(--hue-main), 5%, 60%);
  --color-text--high-contrast: hsl(var(--hue-main), 10%, 80%);
  --color-link: hsl(var(--hue-main), 60%, 60%);
  --color-background: hsl(var(--hue-main), 10%, 12.5%);
}
    <div class="color-scheme-toggle" role="group" title="select a color scheme">
    <p>saved setting: <span class="theme-readout">...</span></p>
        <input type="radio" name="scheme" id="dark" value="dark" aria-label="dark color scheme"> <label for="dark">dark</label>
        <input type="radio" name="scheme" id="system" value="system" aria-label="system color scheme" checked="system"> <label for="system">system</label>
        <input type="radio" name="scheme" id="light" value="light" aria-label="light color scheme"> <label for="light">light</label>
    </div>

I believe the best way is to natively follow system settings unless user says otherwise.我相信最好的方法是本机遵循系统设置,除非用户另有说明。

Create button in your html.在您的 html 中创建按钮。 And then bind three-position switch to it with js.然后用js给它绑定三位开关。 With saving to browser's LocalStorage.保存到浏览器的 LocalStorage。

And, finally, stylize your switch element.最后,对你的开关元素进行风格化。

 document.addEventListener("DOMContentLoaded", function(event) { switchTheme('.theme-switch'); }); function switchTheme(selector) { const switches = document.querySelectorAll(selector); // let colorTheme = localStorage.getItem('colorTheme') || 'system'; //commented to avoid security issue let colorTheme = 'system'; function changeState() { // localStorage.setItem('colorTheme', colorTheme); //commented to avoid security issue document.documentElement.setAttribute('data-theme', colorTheme); } changeState(); switches.forEach(el => { el.addEventListener('click', () => { switch (colorTheme) { case 'dark': colorTheme = 'light'; break case 'light': colorTheme = 'system'; break default: colorTheme = 'dark'; } changeState(); }); }); }
 :root:not([data-theme="dark"]) { --bg: #fff; } @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --bg: #000; } } :root[data-theme="dark"] { /* yep, you'll need to duplicate styles from above */ --bg: #000; } body { background: var(--bg); } .theme-switch:after { content: ': system'; } :root[data-theme="dark"] .theme-switch:after { content: ': dark'; } :root[data-theme="light"] .theme-switch:after { content: ': light'; }
 <button class="theme-switch">Color scheme</button>

I suggest using SCSS.我建议使用 SCSS。 You can make it more simpler.你可以让它更简单。

/* Dark Mode */
@mixin darkMixin {
    body {
        color: #fff; 
        background: #000;
    }
}

@media (prefers-color-scheme: dark) {
    @include darkMixin;
}

.darkMode {
    @include darkMixin;
}

.lightMode {
    body {
        color: #000; 
        background: #fff;
    }
}

And you can toggle/override using JavaScript.您可以使用 JavaScript 切换/覆盖。 (In this example, I used jQuery to make it look easy) (在这个例子中,我使用 jQuery 让它看起来很简单)

// dark
$('html').removeClass('lightMode').addClass('darkMode')

// light
$('html').removeClass('darkMode').addClass('lightMode')

If you want to detect, this is the code based on JimmyBanks' one.如果要检测,这是基于 JimmyBanks 的代码。

function isDarkTheme(){
    let theme="light";    //default to light
    if (localStorage.getItem("theme")){
        if (localStorage.getItem("theme") == "dark")
            theme = "dark"
    } else if (!window.matchMedia) {
        return false
    } else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
        theme = "dark"
    }
    return theme=='dark'
}

To save the current theme, just use localStorage:要保存当前主题,只需使用 localStorage:

localStorage.setItem("theme", 'light')
or
localStorage.setItem("theme", 'dark')

My answer is based on this one , but I have an additional set of changes I had to make to get it working, plus I added code around local storage.我的答案是基于这个,但我必须进行一组额外的更改才能使其正常工作,另外我还添加了有关本地存储的代码。

The important point is that it works with @media (prefers-color-scheme: dark) CSS, and there was need to create or duplicate additional CSS classes just for this to work.重要的一点是它与@media (prefers-color-scheme: dark) CSS 一起工作,并且需要创建或复制额外的 CSS 类才能工作。 In other words it works with native CSS color scheme.换句话说,它适用于原生 CSS 配色方案。

On the page I first added a sun/moon icon and set them to invisible.在页面上,我首先添加了一个太阳/月亮图标并将它们设置为不可见。

<a href="javascript:toggleColorScheme();">
      <span id="icon-sun">🌞</span>
      <span id="icon-moon">🌚</span>
</a>

<style>
#icon-sun {
  width: 1.5rem;
  height: 1.5rem;
  display: none;
}

#icon-moon {
  width: 1.5rem;
  height: 1.5rem;
  display: none;
}
</style>

Then, this Javascript which does the bulk of the work.然后,这个 Javascript 完成大部分工作。

// https://stackoverflow.com/questions/56300132/how-to-override-css-prefers-color-scheme-setting
// Return the system level color scheme, but if something's in local storage, return that
// Unless the system scheme matches the the stored scheme, in which case... remove from local storage
function getPreferredColorScheme(){
  let systemScheme = 'light';
  if(window.matchMedia('(prefers-color-scheme: dark)').matches){
    systemScheme = 'dark';
  }
  let chosenScheme = systemScheme;

  if(localStorage.getItem("scheme")){
    chosenScheme = localStorage.getItem("scheme");
  }

  if(systemScheme === chosenScheme){
    localStorage.removeItem("scheme");
  }

  return chosenScheme;
}

// Write chosen color scheme to local storage
// Unless the system scheme matches the the stored scheme, in which case... remove from local storage
function savePreferredColorScheme(scheme){
  let systemScheme = 'light';

  if(window.matchMedia('(prefers-color-scheme: dark)').matches){
    systemScheme = 'dark';
  }

  if(systemScheme === scheme){
    localStorage.removeItem("scheme");
  }
  else {
    localStorage.setItem("scheme", scheme);
  }

}

// Get the current scheme, and apply the opposite
function toggleColorScheme(){
  let newScheme = "light";
  let scheme = getPreferredColorScheme();
  if (scheme === "light"){
    newScheme = "dark";
  }

  applyPreferredColorScheme(newScheme);
  savePreferredColorScheme(newScheme);


}

// Apply the chosen color scheme by traversing stylesheet rules, and applying a medium.
function applyPreferredColorScheme(scheme) {

  for (var i = 0; i <= document.styleSheets[0].rules.length-1; i++) {
    rule = document.styleSheets[0].rules[i].media;

    if (rule && rule.mediaText.includes("prefers-color-scheme")) {

      switch (scheme) {
        case "light":
          rule.appendMedium("original-prefers-color-scheme");
          if (rule.mediaText.includes("light")) rule.deleteMedium("(prefers-color-scheme: light)");
          if (rule.mediaText.includes("dark")) rule.deleteMedium("(prefers-color-scheme: dark)");
          break;
        case "dark":
          rule.appendMedium("(prefers-color-scheme: light)");
          rule.appendMedium("(prefers-color-scheme: dark)");
          if (rule.mediaText.includes("original")) rule.deleteMedium("original-prefers-color-scheme");
          break;
        default:
          rule.appendMedium("(prefers-color-scheme: dark)");
          if (rule.mediaText.includes("light")) rule.deleteMedium("(prefers-color-scheme: light)");
          if (rule.mediaText.includes("original")) rule.deleteMedium("original-prefers-color-scheme");
          break;
        }
    }
  }

  // Change the toggle button to be the opposite of the current scheme
  if(scheme === "dark"){
    document.getElementById("icon-sun").style.display='inline';
    document.getElementById("icon-moon").style.display='none';
  }
  else {
    document.getElementById("icon-moon").style.display='inline';
    document.getElementById("icon-sun").style.display='none';
  }
}

applyPreferredColorScheme(getPreferredColorScheme());

So on page load, the applyPreferredColorScheme(getPreferredColorScheme()) method runs, which checks the system and local storage and figures out which theme to apply.因此在页面加载时, applyPreferredColorScheme(getPreferredColorScheme())方法运行,它检查系统和本地存储并找出要应用的主题。 It also switches between the sun or moon icon depending on the current theme.它还会根据当前主题在太阳或月亮图标之间切换。

When the user clicks the icon to toggle the theme, toggleColorScheme() runs which stores the chosen theme in local storage, but one difference - if the user switches back to the theme that matches their OS, the code simply removes the item from local storage.当用户单击图标切换主题时, toggleColorScheme()运行,将所选主题存储在本地存储中,但有一个区别 - 如果用户切换回与其操作系统匹配的主题,代码只是从本地存储中删除该项目. Trying to keep it as native as possible.尽量保持原汁原味。

Looks like there's plenty of answers here already, but nothing quite matching my needs.看起来这里已经有很多答案,但没有一个完全符合我的需要。 I wanted to be able to:我希望能够:

  • Use the OS preference if no other preference is set.如果未设置其他首选项,请使用操作系统首选项。
  • If using the OS preference, have the page reflect changes to it instantly.如果使用操作系统首选项,让页面立即反映对其的更改。
  • Allow a query string param to override the OS preference.允许查询字符串参数覆盖操作系统首选项。
  • If the user explicitly sets a preference, use that and save for future use.如果用户明确设置首选项,则使用该首选项并保存以备将来使用。
  • Have the checkbox on the page reflect the theme currently in effect.让页面上的复选框反映当前有效的主题。
  • Toggle the same checkbox is explicitly set a preference.切换相同的复选框是明确设置的首选项。
  • Have relatively short but clean and readable code.具有相对较短但干净且可读的代码。

My CSS looks a lot like that in other solutions.我的 CSS 在其他解决方案中看起来很像。 For completeness this is something like:为了完整起见,这类似于:

:root {
    /* Support light and dark, with light preferred if no user preference */
    color-scheme: light dark;

    /* default light mode */
    --bg-color: #f7f7f7;
    --text-color: #222430;

    /* dark mode overrides */
    --dark-bg-color: #222430;
    --dark-text-color: #f7f7f7;
}

.dark-theme {
    /* Use dark mode overrides when class is applied */
    --bg-color: var(--dark-bg-color);
    --text-color: var(--dark-text-color);
}

body {
    background-color: var(--bg-color);
    color: var(--text-color);
}

There is a checkbox with id 'darkThemeToggle' in the HTML. The below runs on page load. HTML 中有一个 id 为“darkThemeToggle”的复选框。以下在页面加载时运行。 (Written in TypeScript) (用打字稿写的)

function manageTheme() {
  const darkThemeCheckbox = document.querySelector("#darkThemeToggle") as HTMLInputElement;
  const agentPrefersDarkQuery = matchMedia("(prefers-color-scheme: dark)");

  function setTheme(arg?: MediaQueryListEvent | string) {
    let chosenTheme = "";

    if (typeof arg === "string") {
      // If this function is called with a string, then an explict preference has
      // been set by the user. Use that theme and save the setting for the future.
      chosenTheme = arg;
      localStorage.setItem("theme", chosenTheme);
    } else {
      // Use any saved preference, else check for query param, else any OS preference.
      chosenTheme = localStorage.getItem("theme") || 
          new URLSearchParams(window.location.search).get("theme") ||
          (agentPrefersDarkQuery.matches ? "dark" : "light");
    }

    if (chosenTheme === "dark") {
      document.documentElement.classList.add("dark-theme");
    } else {
      document.documentElement.classList.remove("dark-theme");
    }

    // Update the UX to reflect the theme that was ultimately applied.
    darkThemeCheckbox.checked = (chosenTheme === "dark");
  }

  // Whenever the user changes the OS preference, refresh the applied theme.
  agentPrefersDarkQuery.onchange = setTheme;

  // Note that the 'change' event only fires on user action, (not when set in code), which is
  // great, else this might cause an infinite loop with the code setting it in setTheme.
  darkThemeCheckbox.addEventListener('change', ev => {
    let themeChosen = darkThemeCheckbox.checked ? "dark" : "light";
    setTheme(themeChosen);
  });

  setTheme(); // Run on initial load.
}

document.addEventListener("DOMContentLoaded", manageTheme);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM