黑暗模式 - 如何处理自动偏好
Dark mode - how to handle the auto preference
在这个暗模式代码中,我允许用户设置 theme
到 system 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
设置,您应该有以下行为:
light
用于将覆盖 system/browser 设置的灯光方案
dark
用于将覆盖 system/browser 设置的深色方案
- 除
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
在这个暗模式代码中,我允许用户设置 theme
到 system 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
设置,您应该有以下行为:
light
用于将覆盖 system/browser 设置的灯光方案dark
用于将覆盖 system/browser 设置的深色方案- 除
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