如何覆盖css偏好颜色方案设置
Posted
技术标签:
【中文标题】如何覆盖css偏好颜色方案设置【英文标题】:How to override css prefers-color-scheme setting 【发布时间】: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?我会包含我尝试过的代码,但我找不到任何关于实现它的信息!
【问题讨论】:
【参考方案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;
<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
会根据操作系统主题的更改自动更新站点的主题。
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】:您可以使用我的自定义元素 <dark-mode-toggle>
,它最初遵循用户的 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
<button onclick="toggle()">Toggle Light/Dark Mode</button>
【讨论】:
【参考方案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">:rootcolor-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">:rootcolor-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">:rootcolor-scheme:dark</style>'),
document.body.classList.add("dark"),
localStorage.removeItem("scheme"))
: (document.getElementById("scheme").remove(),
document.head.insertAdjacentHTML("beforeend", '<style id="scheme">:rootcolor-scheme:light</style>'),
document.body.classList.remove("dark"),
localStorage.setItem("scheme", 1));
))
: (document.head.insertAdjacentHTML("beforeend", '<style id="scheme">:rootcolor-scheme:light</style>'),
e && (e.checked = !1),
window.localStorage.getItem("scheme") &&
(document.getElementById("scheme").remove(), document.head.insertAdjacentHTML("beforeend", '<style id="scheme">:rootcolor-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">:rootcolor-scheme:dark</style>'),
document.body.classList.add("dark"),
localStorage.setItem("scheme", 1))
: (document.getElementById("scheme").remove(),
document.head.insertAdjacentHTML("beforeend", '<style id="scheme">:rootcolor-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()
,因此您将无法测试系统更改的实时更新。不要犹豫,在您的本地主机上尝试一下。
【讨论】:
“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';
<button class="theme-switch">Color scheme</button>
【讨论】:
【参考方案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);
详情
此解决方案假定:
-
无论在系统偏好设置(暗/亮模式)中设置什么,都将被确认为初始模式
从初始模式开始,最终用户可以手动切换暗模式或亮模式
如果系统没有深色模式功能,将使用浅色模式主题
无论最终用户之前手动设置的主题(深色/浅色模式)如何,都将是下一页重新加载/刷新时的新初始模式
【讨论】:
【参考方案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')
【讨论】:
以上是关于如何覆盖css偏好颜色方案设置的主要内容,如果未能解决你的问题,请参考以下文章