如何克服CSS偏爱颜色方案设置

时间:2019-05-24 22:28:14

标签: javascript html css media-queries

我正在实现一种暗模式,就像macOS,Windows一样,并且iOS有望引入本机暗模式。

使用以下CSS媒体规则,SafariChromeFirefox有一个本机选项:

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

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

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

是否有一种可以使用HTML5或JavaScript执行的方法?我会包含尝试过的代码,但无论如何都找不到任何信息!

8 个答案:

答案 0 :(得分:2)

这是一个遵循默认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}
}
<button onclick="toggle()">Toggle Light/Dark Mode</button>

答案 1 :(得分: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 {{ 1}}:

<body>...</body>

此javascript用于在设置之间切换,它不需要包含在页面标题中,但是可以包含在任何地方

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

最后,HTML复选框可在主题之间切换:

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

通过使用CSS变量和JavaScript,我们可以自动确定用户主题,应用主题并允许用户也改写它。 [截至撰写本文时(2019/06/10),仅Firefox和Safari支持自动主题检测。

答案 2 :(得分:1)

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

答案 3 :(得分:0)

我将为深色主题创建第二个CSS文件并使用

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

然后,如果用户选择了暗模式,则使用JavaScript使用these行添加内容。如果为link元素指定ID(或全局范围),则也可以使用JS删除它。

答案 4 :(得分:0)

采用@JimmyBanks提供的解决方案,1)将复选框变成一个切换文本按钮,然后2)添加了自动切换OS主题的主题。

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

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

这是对JS第二个代码块以及相关HTML的编辑。 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>

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

答案 5 :(得分:0)

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

我猜它有点冗长,但是我有点挣扎着把它包裹住

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>

答案 6 :(得分:0)

以上都不适合我。我决定从不同的角度来解决这个问题。年份是 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,一个简单的复选框 <input id="tglScheme" type="checkbox">

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

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

答案 7 :(得分:0)

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

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

最后,风格化你的开关元素。

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>