网站适配暗色模式并实现手动、自动切换并存

本文最后更新于 2023年9月17日 上午

文章简介

现在,常见的操作系统,基本都已经适配了暗色/亮色模式,并提供API接口:

  • macOS Mojave 10.14 开始提供了外观设置选项,支持设置 浅色 / 深色 外观。
  • Windows10 1809版开始支持亮色/暗色主题风格。
  • Android 10 (API 级别 29) 开始支持深色主题背景(第三方OEM厂商可能有所差异)。
  • iOS13开始全面支持暗色模式。


那么,我们自己的网站如何适配暗色/亮色模式呢?首先说一下最基础的媒体查询,然后带大家了解一下我的适配方案(纯JSCSS的前端操作)。
效果图

本文章同步发布在:

媒体查询的优缺点

高版本的浏览器,可以提供媒体查询功能,我们使用CSS,加入媒体判断即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 常规浅色模式下的网页背景颜色及文本颜色 */
body {
background: #fff;
color: #222;
}

/* 深色模式下的网页背景颜色及文本颜色 */
@media (prefers-color-scheme: dark) {
body {
background-color: #222;
color: #ddd;
}
}

prefers-color-scheme支持三个值,分别是 :

  • no-preference:无指定
  • light:亮色
  • dark:暗色
    这样的好处是适配快,但是坏处也有,主要的体现是无法用户主动切换
    举个例子,有些用户习惯把系统长期设置为暗色模式,访问你网站时,想看清你网站的图片,希望调整成亮色模式,却必须到系统设置内,手动把系统配色调成亮色再刷新网站,体验差
    当时还好,我们有JS,使用JS也可以媒体查询,我们就不需要用CSS来媒体查询系统暗色亮色配色:
    1
    2
    // JS查询是系统是否为暗色配色
    matchMedia('(prefers-color-scheme: dark)').matches
    下文,我们开始给网站适配。

适配逻辑

本次适配的适配暗色/亮色模式的用户操作逻辑分两种情况:存在暗色模式标识符不存在暗色模式标识符
暗色标识符:由暗色/亮色按钮调用的JS实现存储在CookieslocalStorage内,用来提示JS展现那种页面配色。
暗色/亮色的现实主要是,当需要给用户展现网站暗色配色时,在HTML内<body>标签内加入class="night"

不存在暗色模式标识符

用户进入网站,若之前没有手动点击网站上切换暗色/亮色按钮(不存在暗色模式标识符),则使用媒体查询检测用户是否有开启暗色模式,同步系统配色。
同时,媒体查询存在一定的兼容性问题,浏览器版本过低(如:IE 9),在查询失败时:
则逻辑判断用户当前系统时间,根据时间显示暗色亮色配色。

存在暗色模式标识符

若用户之前有点击过切换暗色/亮色按钮,查询CookieslocalStorage内暗色模式标识符来展示暗色/亮色配色,优先级高于媒体查询时间判断

HTML结构

首先,我们依靠<body>标签内是否存在class="night"来实现,所以JS引用,最好紧接<body>标签:

1
2
3
4
5
<body>
<!--切换`亮色/暗色`按钮,加上"小埋皮肤"-->
<i onclick="switchNightMode()" id="xm"></i>
<!--`亮色/暗色`逻辑判断JS"-->
<script type="text/javascript" src="/DarkMode/js/switch.js"></script>

1
<i onclick="switchNightMode()" id="xm"></i>

这条是我实现的切换暗色/亮色按钮,大家可以根据自己需要进行替换:

CSS结构

因为暗色模式的现实是依靠<body>标签内是否存在class="night"来实现:

1
2
3
4
.night {
color: #E0E0E0;
background-color: #424242;
}

派生

因为我们是直接在<body>标签内添加CSS叠加,所以,按照样式的优先级来说,在CSS内使用,我们就需要使用派生方法写样式,如:

1
2
3
4
5
6
7
8
9
10
/*暗色模式span标签*/
.night span {
color: #E0E0E0;
}
/*暗色模式警告按钮样式*/
.night .btn-danger {
color: #fff;
background-color: #e45353a1;
border-color: #e45353a1;
}

以此类推,根据自己亮色模式下的样式,适配暗色模式即可。

图片处理

另外,为了让暗色模式下,图片不要过度亮而刺眼,我们添加filter样式:

1
2
3
.night img{
filter: brightness(0.9);
}

JS结构

JS结构就比较复杂了,主要分三个部分

进入网站,判断是否启动暗色模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//检查当前主题模式和系统主题是否对应Start
function checkNightMode() {
var Mode = document.cookie.split(";")[0].split("=")[1];
cookiesExp=new Date(new Date().setMonth(new Date().getMonth()+1));
//存在暗色模式标识符,且Cookies中DarkMode值为1
if (Mode == 1) {
// 值为1:添加class="night"到body标签"
$("body").addClass("night");
}
//不存在暗色模式标识符情况下,是否需要启用暗色模式
else if (Mode == null || Mode == "undefined" || Mode == "") {
// 媒体查询,用户系统是否启动暗色模式
if (matchMedia('(prefers-color-scheme: dark)').matches) {
$("body").addClass("night");
}
// 媒体查询,用户系统是否启动亮色模式
else if (matchMedia('(prefers-color-scheme: light)').matches) {
$("body").removeClass("night");
}
// 时间判断,是不是到点了( ;´Д`)
else if (new Date().getHours() >= 21 || new Date().getHours() < 7) {
$("body").addClass("night");
}
}

}
//检查当前主题模式和系统主题是否对应End

这个JS是在用户进入网站,加载到<body>标签时,进行判断,是否需要在表情内加入class="night"。先判断是否有标识符,再判断媒体查询结果,最后判断用户系统时间,优先级逐级递减。

用户主动切换按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 切换暗亮模式Start
function switchNightMode() {
// 获取Cookies内DarkMode值
var Mode = document.cookie.split(";")[0].split("=")[1];
// 如果DarkMode值不存在,也就是不存在标识符
if (Mode == null || Mode == "undefined" || Mode == "") {
// 判断是否处于暗色模式
if ($("body").hasClass("night")) {
// 处于暗色模式,删除标签内night
$("body").removeClass("night");
// 添加标识符
document.cookie = "DarkMode=0;path=/" + ";expires=" + cookiesExp.toGMTString();
$('#nightMode').removeClass("icon-yueliang").addClass("icon-zhishifufeiqiapianicon-");
} else {
// 不处于暗色模式,添加标签
$("body").addClass("night");
// 添加标识符
document.cookie = "DarkMode=1;path=/" + ";expires=" + cookiesExp.toGMTString();
$('#nightMode').removeClass("icon-zhishifufeiqiapianicon-").addClass("icon-yueliang");
}
} else if (Mode == '0') {
// 标识符为0,代表之前没开启暗色模式
$("body").addClass("night");
document.cookie = "DarkMode=1;path=/" + ";expires=" + cookiesExp.toGMTString();
$('#nightMode').removeClass("icon-zhishifufeiqiapianicon-").addClass("icon-yueliang");
} else {
$("body").removeClass("night");
document.cookie = "DarkMode=0;path=/" + ";expires=" + cookiesExp.toGMTString();
$('#nightMode').removeClass("icon-yueliang").addClass("icon-zhishifufeiqiapianicon-");
}
}
// 切换暗亮模式End

用户主动切换按钮,是在用户点击某个元素,触发onclick函数事件,这边为还替换了页面id为nightMode标签内的图标:

系统配色切换监听

其实,配置已经基本配置完成,但是如果用户设置的是自动变换配色,如Mac用户的外观自动

在系统自动切换暗色/亮色的同时,如何让网站也一同切换?
答案就是创建监听器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 监听暗色、亮色切换Start
let lightMedia = window.matchMedia('(prefers-color-scheme: light)');
let darkMedia = window.matchMedia('(prefers-color-scheme: dark)');
let callback = (e) => {
let prefersDarkMode = e.matches;
if (prefersDarkMode) {
checkNightMode();
}
};
if (typeof darkMedia.addEventListener === 'function'||typeof lightMedia.addEventListener === 'function') {
lightMedia.addEventListener('change', callback);
darkMedia.addEventListener('change', callback);
}
// 监听暗色、亮色切换End

这样就可以在用户系统更改配色的同时,网站随之更改了。

Demo

最后,可以给搭建看看适配好的效果图网站:https://image.mintimate.cn

Tips

本次适配,标识符存储在Cookies内,且设置切换一次后,有效期为30天,实际生产环境中,存储在localStorage内可能是一个更好的选择。



网站适配暗色模式并实现手动、自动切换并存
https://www.mintimate.cn/2021/02/01/webAutoDarkMode/
作者
Mintimate
发布于
2021年2月1日
许可协议