【问题标题】:How to override css prefers-color-scheme setting如何覆盖css偏好颜色方案设置
【发布时间】:2019-10-11 11:58:01
【问题描述】:

我正在实现暗模式,因为 macOS、Windows 和 iOS 都引入了暗模式。

Safari、Chrome 和 Firefox 有一个原生选项,使用以下 CSS 媒体规则:

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

这将自动识别设置为暗模式的系统,并应用随附的 CSS 规则。

但是;即使用户可能将他们的系统设置为暗模式,他们也可能更喜欢特定网站的浅色或默认主题。还有一种情况是 Microsoft Edge 用户(尚)不支持@media (prefers-color-scheme。为了获得最佳用户体验,我想确保这些用户可以在这些情况下在暗模式和默认模式之间切换。

是否有可以执行此操作的方法,可能使用 HTML 5 或 JavaScript?我会包含我尝试过的代码,但我找不到任何关于实现它的信息!

【问题讨论】:

    标签: javascript html css media-queries


    【解决方案1】:

    我已经确定了一个合适的解决方案,如下:

    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 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 用于识别用户设置了哪个主题,或者他们是否覆盖了他们的操作系统主题,以及在两者之间切换,这包含在 html @987654323 输出之前的标题中@:

    //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();
    

    此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;
    }
    

    最后,用于在主题之间切换的 HTML 复选框:

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

    通过使用 CSS 变量和 JavaScript,我们可以自动确定用户主题,应用它,并允许用户覆盖它。 [截至目前(2019/06/10),只有 Firefox 和 Safari 支持自动主题检测]

    【讨论】:

    • 您不需要 Javascript,只需使用已经具有“toggly”行为的原生 html 元素,例如复选框,然后使用相邻的兄弟选择器或后代选择器以及 :checked用于在颜色模式之间切换的伪选择器。
    • @SethWarburton 只要您不重新加载站点,这将起作用。如果要存储用户设置,则需要 JS。
    【解决方案2】:

    采用@JimmyBanks 提供的解决方案,1)将复选框变成一个切换文本按钮,2)在操作系统主题更改时添加自动主题切换。

    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;
    }
    

    &lt;head&gt; 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';
    }
    

    这里是对第二个 JS 块的编辑,以及相关的 HTML。 theme_switch 切换主题,而theme_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>
    

    如果您有任何改进建议,请告诉我!

    【讨论】:

      【解决方案3】:

      您可以使用我的自定义元素 &lt;dark-mode-toggle&gt;,它最初遵循用户的 prefers-color-scheme 设置,但也允许用户(永久或临时)覆盖它。切换既适用于单独的 CSS 文件,也适用于切换的类。 README 提供了两种方法的示例。

      【讨论】:

        【解决方案4】:

        我的解决方案(无线电输入中的 3 个选项:暗、系统、亮)改编自 JimmyBanks 和 Meanderbilt 解决方案:

        我猜它有点冗长,但我有点费力地绕着它转

        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>
        

        【讨论】:

          【解决方案5】:

          这是一个尊重默认prefers-color-scheme 的答案,然后才允许您通过localStorage 进行切换。这节省了通过 JS 找出默认方案所需的时间,而且即使没有 JS,人们也会使用默认方案。

          我不喜欢必须声明默认样式(我选择了 Dark),然后将其重新声明为名为 dark-mode 的类,但它是 unavoidable

          请注意,此论坛似乎屏蔽了 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}
          }
          &lt;button onclick="toggle()"&gt;Toggle Light/Dark Mode&lt;/button&gt;

          【讨论】:

            【解决方案6】:

            以上都不适合我。我决定从不同的角度来处理这个问题。年份是 2021 年。


            以下优惠:

            • 尊重系统偏好。
            • 系统偏好设置被覆盖。
            • 尊重滚动条配色方案。
            • 通用浏览器支持。(IE 生命周期结束,2021 年 8 月 17 日?✌️?)

            当您查看MDN Web Docs page for prefers-color-scheme 时,您可以阅读以下内容:

            prefers-color-scheme CSS 媒体功能用于检测用户是否请求了浅色或深色主题。 [...]

            light 表示用户已通知他们更喜欢具有浅色主题的界面,或尚未表达积极的偏好

            因此,对于任何浏览器,默认情况下,prefers-color-scheme 要么设置为 light,要么不受支持。

            我在接受答案时遇到的一个问题是更改不会影响滚动条颜色。这可以使用与:root 伪元素耦合的color-scheme CSS property 来处理。

            我遇到的另一个问题是,如果用户将系统设置更改为亮或暗,则网站不会受到影响,并且会在两种样式之间产生不匹配。我们可以通过将window.matchMedia( '(prefers-color-scheme: light)' ) 耦合到onchange 事件侦听器来修复该行为。

            这是最终的脚本。

            (() => {
                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");
            });
            

            对于 CSS 方面,我们使用默认的 variable custom property values fallback,并将深色放在首位。我们可以通过 :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, fallback to light. */
            

            对于 html,一个简单的复选框 &lt;input id="tglScheme" type="checkbox"&gt;

            最后是 Codepen https://codepen.io/amarinediary/full/yLgppWW

            Codepen 会覆盖location.reload(),因此您将无法测试系统更改的实时更新。不要犹豫,在您的本地主机上尝试一下。

            【讨论】:

            • “IE 生命周期结束,2021 年 8 月 17 日?✌️?” - 多么美好的时光。
            【解决方案7】:

            我认为最好的方法是本机遵循系统设置,除非用户另有说明。

            在您的 html 中创建按钮。然后用js给它绑定三位开关。保存到浏览器的LocalStorage。

            最后,对你的 switch 元素进行风格化。

            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';
            }
            &lt;button class="theme-switch"&gt;Color scheme&lt;/button&gt;

            【讨论】:

              【解决方案8】:

              不确定,为什么所有的答案都这么复杂。

              像往常一样在媒体查询中使用 CSS 变量、设置默认值和相反的值。还要在两个类中设置值。实现一个在单击时切换这些类的开关。

              默认情况下,根据系统配色方案使用自动明暗模式。使用拨动开关切换到手动亮/暗模式。刷新页面(或从 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('(prefers-color-scheme: dark)').matches) {
                    document.documentElement.classList.add("light")
                  } else {
                    document.documentElement.classList.add("dark")
                  }
                }
              }
              /* 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>

              【讨论】:

              • 通俗易懂。唯一缺少的是在本地存储中存储主题的功能。
              • 我对此有点陌生,我尝试将此代码复制粘贴到我的 wordpress 主题中,但它什么也没做,除了给我错误:规则为空且预期为 RBRACE。我应该用特定代码替换“--some-value”吗?
              【解决方案9】:

              TL;DR


              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);
              

              详情

              此解决方案假定:

              1. 无论在系统偏好设置(暗/亮模式)中设置什么,都将被确认为初始模式
              2. 从初始模式开始,最终用户可以手动切换暗模式或亮模式
              3. 如果系统没有深色模式功能,将使用浅色模式主题
              4. 无论最终用户之前手动设置的主题(深色/浅色模式)如何,都将是下一页重新加载/刷新时的新初始模式

              【讨论】:

                【解决方案10】:

                我建议使用 SCSS。你可以让它更简单。

                /* 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;
                    }
                }
                

                您可以使用 JavaScript 切换/覆盖。 (在这个例子中,我使用 jQuery 让它看起来很简单)

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

                如果要检测,这是基于 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'
                }
                

                要保存当前主题,只需使用 localStorage:

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

                【讨论】:

                  猜你喜欢
                  • 2019-10-17
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-04-29
                  • 1970-01-01
                  • 2011-01-27
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-03-14
                  相关资源
                  最近更新 更多