黑暗模式 - 如何处理自动偏好

Dark mode - how to handle the auto preference

在这个暗模式代码中,我允许用户设置 themesystem preferences,同时还允许用户通过切换 button 或 [=16] 来覆盖系统首选主题=].

当用户将他们的系统首选项更改为 auto 时,如何为 system default 收音机设置 checked 状态?

因为它将 prefers-color-scheme 更改为亮或暗,我如何检测用户将他们的偏好更改为 auto 以更新收音机的 checked 状态?我尝试在 if (window.matchMedia("(prefers-color-scheme: no-preference)").matches) {} 中添加 checked 状态,但它没有执行。

我也不知道为什么我的 window.addEventListener("load", (e) => {} 在加载时没有检测到用户选择的主题。它默认为 light 即使页面刷新并且设置中的主题设置为深色。

https://codepen.io/moofawsaw/pen/VwmaPMV

$(document).ready(function() {
  function setLight() {
    $("body").removeClass("dark-theme");
    $("body").addClass("light-theme");
    $("#light").prop("checked", true);
    $('input[name="theme"]').change();
  }

  function setDark() {
    $("body").addClass("dark-theme");
    $("body").removeClass("light-theme");
    $("#dark").prop("checked", true);
    $('input[name="theme"]').change();
  }

  function setMode() {
    if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
      $("#dark").prop("checked", true);
      $('input[name="theme"]').change();
      setDark();
    } else {
      $("#light").prop("checked", true);
      $('input[name="theme"]').change();
      setLight();
    }
  }
  //Check the mode on load and style accordingly
  if (localStorage.getItem("mode") == "dark-theme") {
    setDark();
  } else {
    setLight();
  }
  //Check for when system default is changed -> change theme
  window
    .matchMedia("(prefers-color-scheme: dark)")
    .addEventListener("change", (e) => {
      if (localStorage.getItem("mode") !== null) {
        //Manually set so don't do anything
      } else {
        setMode();
      }
    });
  window.addEventListener("load", (e) => {
    //If the mode is mannually set on load - choose that mode
    if (localStorage.getItem("mode") !== null) {
      // Do nothing the mode has been set manually
    } else {
      // Set the mode to the default
      setMode();
    }
  });
  //add toggle
  $("#toggleTheme").on("click", function() {
    if ($("body").hasClass("dark-theme")) {
      localStorage.setItem("mode", "light-theme");
      setLight();
    } else {
      localStorage.setItem("mode", "dark-theme");
      setDark();
    }
  });
  $("#light").on("click", function() {
    localStorage.setItem("mode", "light-theme");
    setLight();
  });
  $("#dark").on("click", function() {
    localStorage.setItem("mode", "dark-theme");
    setDark();
  });
  $("#default").on("click", function() {
    localStorage.removeItem("mode");
    if (localStorage.getItem("mode") !== null) {
      setMode();
    }
  });
});

//For selecting radio
$('input[name="theme"]').on("change", function() {
  if ($(this).is(":checked")) {
    $("label.checked").removeClass("checked");
    $(this).closest("label").addClass("checked");
  }
});
body {
  --font-color: blue;
  --bg-color: white;
  --bg-span: #ececec;
}

body.dark-theme {
  --font-color: white;
  --bg-color: black;
  --bg-span: white;
}

@media (prefers-color-scheme: dark) {
  body {
    --font-color: white;
    --bg-color: black;
    --bg-span: white;
  }
  body.light-theme {
    --font-color: blue;
    --bg-color: white;
    --bg-span: #ececec;
  }
}

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

button {
  padding: 0.3rem 0.9rem;
  outline: none;
  color: var(--font-color);
  background: var(--bg-color);
}

span {
  padding: 0.9rem;
  color: var(--font-color);
  background: var(--bg-span);
}

img {
  max-width: 190px;
}


/*input {
  display: none;
}*/

label {
  overflow: hidden;
  display: flex;
  flex-direction: column;
  margin-right: 0.9rem;
  border: 1px solid whitesmoke;
  border-radius: 9px;
}

label.checked {
  border: 3px solid blue;
}

.theme {
  display: flex;
}

.buttons,
.theme {
  padding: 1.3rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="buttons">
  <button id="toggleTheme">Mode</button>
</div>

<div class="theme">
  <label for="light">
    <img src="https://github.githubassets.com/images/modules/settings/color_mode_light.svg">
    <input type="radio" name="theme" id="light">
    <span>Light</span>
  </label>
  <label for="dark">
    <img src="https://github.githubassets.com/images/modules/settings/color_mode_dark.svg">
    <input type="radio" name="theme" id="dark">
    <span>Dark</span>
  </label>
  <label for="default">
    <img src="https://github.githubassets.com/images/modules/settings/color_mode_auto.svg">
    <input type="radio" name="theme" id="default">
    <span>System Default</span>
  </label>
</div>

prefers-color-scheme @media 规则 is deprecated 的特性 no-preference,这就是为什么它不再适用于最新的浏览器。

load window 事件确实执行了,但它在您的代码中没有适当的逻辑。如果 mode 本地存储设置不是 null,您的代码将不执行任何操作,这当然是在 selecting 之后的情况。

关于 mode 设置,您应该有以下行为:

  1. light 用于将覆盖 system/browser 设置的灯光方案
  2. dark 用于将覆盖 system/browser 设置的深色方案
  3. dark/light 以外的任何其他内容,包括 null/undefined,都将应用浏览器设置,它可以有自己的覆盖或读取OS 设置

此外,setLight/setDark 程序应该只应用主题而不改变任何输入元素状态,因为它最终会出现循环值设置错误:

function setLight() {
  $("body").removeClass("dark-theme");
  $("body").addClass("light-theme");
}

function setDark() {
  $("body").addClass("dark-theme");
  $("body").removeClass("light-theme");
}

相反,您可以让 setMode 内的所有输入更改逻辑,它应该检查 mode 设置并处理所有逻辑,如下所示:

function setMode() {
  // have a variable to indicate what mode
  // we should apply at the end of the procedure
  let lightMode;

  // always unregister media listener,
  // because we only need it if mode is not set,
  // which then we'll have to read media queries using matchMedia
  unregisterMediaListener();

  switch (localStorage.getItem("mode")) {
    case 'light-theme': {
      lightMode = true;
      $("#light").prop("checked", true);
      break;
    }
    case 'dark-theme': {
      lightMode = false;
      $("#dark").prop("checked", true);
      break;
    }
    default: { // using system setting
      $("#default").prop("checked", true);
      // set lightMode indicator using the matchMedia query result
      lightMode = window.matchMedia("(prefers-color-scheme: light)").matches;
      // and register media change listener for changes
      registerMediaListener();
    }
  }

  // allpy new input values
  $('input[name="theme"]').change();

  // set the actual mode
  if (lightMode) {
    setLight();
  } else {
    setDark();
  }
}

最后 matchMedia prefers-color-scheme: light 更改侦听器:

// we set a mediaMatch variable so we can use it and unregister the listener
let mm;

function registerMediaListener() {
  mm = window.matchMedia("(prefers-color-scheme: light)");
  mm.addEventListener('change', onLightSchemeChange);
}

function unregisterMediaListener() {
  if (mm) mm.removeListener(onLightSchemeChange);
}

function onLightSchemeChange(scheme) {
  if (scheme.matches) {
    // We have browser/system light scheme
    setLight();
  } else {
    setDark();
  }
}

工作示例

您可以检查这个分叉和修改过的笔,它确实在工作,并且在页面加载时 select 执行适当的 setting/option:https://codepen.io/clytras/pen/MWberor