前言
早在IOS13、macOS10.13之后,暗黑/深色模式逐步开始遍地开花,目前适配深色模式已经是必备操作了。主题模式一般分为浅色模式、深色模式,有的叫法是亮色、暗色。
实现切换主题模式
传统通过css class实现
很简单的实现方式,只需要在body添加一个类名,比如"dark"
,当点击切换主题的按钮时,为body增加/删除“dark”类名即可实现,同时可以使用css变量定义全局的配色方案。
示例::
CSS:
body {
--color-background: #f1f2f3;
--color-text1: #000;
}
body.dark {
--color-background: #151515;
--color-text1: #fff;
}
.list {
background: var(--color-background);
}
JS:
document.getElementsByTagName("body")[0].classList.add("dark"); // 添加dark类
document.getElementsByTagName("body")[0].classList.remove("dark"); // 删除dark类
不过依然只能手动切换不能自动跟随系统。
通过新属性:color-scheme
写法
为网页声明color-scheme,有两种写法,一种是在头部
中添加一段<meta name="color-scheme" content="light dark">
,另一种是在css里声明,例如::root { color-scheme: light dark; }
两者皆可,不过我比较喜欢后者,毕竟样式就该归样式管理。
解释
color-scheme
这个css属性在MDN的解释是:css color-scheme允许元素指示它可以轻松呈现的配色方案。
操作系统配色方案的常见选择是“亮”和“暗”,或者是“白天模式”和“夜间模式”。当用户选择其中一种配色方案时,操作系统会对用户界面进行调整。这包括表单控件、滚动条和 CSS 系统颜色的使用值。
(其实也就是表示该网页支持哪些主题模式),它的取值主要有四种:
normal
、light
、dark
、light dark
。- normal:使用浏览器的默认配色方案渲染元素。
- light:支持使用操作系统亮色配色方案渲染元素。
- dark:支持使用操作系统深色配色方案渲染元素。
- light dark:支持跟随系统使用亮色或深色方案渲染元素。
不过需要注意的是设定了这个属性只是让浏览器的默认样式跟随变化了,你所编写的页面内容并不会做出改变。
color-scheme媒体查询
color-scheme本身并不能直接帮我们适配,不过所幸它可以通过css媒体查询匹配,因此我们可以使用媒体查询去适配配色方案。
- 匹配dark深色模式:
@media(prefers-color-scheme: dark) {}
- 匹配lignt浅色模式:
@media(prefers-color-scheme: light) {}
使用示例:
:root { color-scheme: light dark; } /* 深色主题配色 */ @media (prefers-color-scheme: dark) { :root { --color-primary: #369dec; --color-secondary: #2c7dbb; --color-surface: #202020; --color-surfaceVariant: #333; --color-background: #151515; --color-text0: #fff; --color-text1: #eee; --color-text2: #ccc; --color-text3: #aaa; --color-text4: #888; --color-text5: #666; } }
这样一起搭配就可以实现跟随系统自动切换主题模式了。
但是如果在这个基础上要求既能自动跟随系统切换,又要能手动切换呢?
例如页面首次加载要跟随使用系统主题模式,后续用户手动点击切换。
回想到color-scheme可以单独设置light或者dark,那么能否通过变更这个属性值这个去实现呢?可惜,绝大部分的浏览器并不支持color-scheme的light only与dark only模式(参考自MDN),所以无法只通过它实现主题模式自动+手动切换共存。
既然如此,那么只使用css肯定是不够用的,还需要使用JavaScript去实现。
- 匹配dark深色模式:
CSS color-scheme + JS搭配
判断当前操作系统是否处于深色模式
不管怎么适配,都逃不开获取操作系统当前的主题模式,但似乎Web并没有直接提供这个函数或变量?回想到媒体查询可以匹配到浅色和深色模式,那么只要JS能获取到媒体查询结果不就可以了?
刚好就有一个函数可以匹配媒体查询:
window.matchMedia(String)
,传入媒体查询的条件,返回匹配结果。例如window.matchMedia("screen and (min-width: 800px)")
匹配屏幕长度是否大于等于800px(记得带上括号,否则匹配结果一直是false)。匹配结果如下:{ media: 'screen and (min-width: 800px)', matches: true, onchange: null }
于是,匹配操作系统是否处于暗黑模式就可以这样写:
// 媒体查询暗黑模式 const darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); if (darkModeQuery.matches) { // 处于深色模式 } else { // 处于浅色模式 }
操作系统切换主题模式时触发的事件监听
将window.matchMedia与change事件结合起来就可以实现,
// darkModeQuery接上文 darkModeQuery.addEventListener("change", (e) => { console.log(e.matches ? "深色模式" : "浅色模式"); });
试验结果:
在项目中的实践(Vue3 + TypeScript):
定义一个切换主题模式的工具类
// theme-utils.ts /** 媒体查询深色模式 */ const darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)"); /** 切换至浅色模式 */ export function switchToLightMode(): void { document.documentElement.classList.remove("dark"); } /** 切换至深色模式 */ export function switchToDarkMode(): void { document.documentElement.classList.add("dark"); } /** 操作系统是否正处于深色模式 */ export function isDarkModeInSystem(): boolean { return darkModeQuery.matches; } /** 当前网页是否正处于深色模式 */ export function isDarkModeInWeb(): boolean { return document.documentElement.classList.value.includes("dark"); } export { darkModeQuery };
使用工具类
// App.vue, <script setup lang="ts"> import { darkModeQuery, isDarkModeInSystem, switchToLightMode, switchToDarkMode, } from "@/utils/theme-utils"; // 系统切换主题模式时触发的事件函数 function darkModeHandler() { isDarkModeInSystem() ? switchToDarkMode() : switchToLightMode(); } darkModeHandler(); // 网页首次加载时设置为操作系统的主题模式 darkModeQuery.addEventListener("change", darkModeHandler); // 系统的主题模式变化时触发事件监听
至于html按钮的点击切换主题模式,这里就不细写了。
另附stylus的一种写法:
.button {
…
&:hover {
--button-background: #ae3937;
@media (prefers-color-scheme: dark) {
--button-background: #2e98d1;
--button-text: var(--background-color);
}
}
}
参考资料:
color-scheme - CSS(层叠样式表) | MDN (mozilla.org)
https://cloud.tencent.com/developer/article/1841798?from=15425