仅需几行代码,为网站添加黑暗模式
March 08, 2020
前言
在上一年,黑暗模式的概念席卷而来,随着系统级别的支持,其他主流应用程序的适配也陆续展开,它们大多提供了相应的入口,让用户可以切换整个主题,以此获得最舒适的体验。
为什么要使用黑暗模式?
这个问题,我刚开始也不明白,黑乎乎的界面不仅难辨识,而且还加重开发和 UI 的负担,但回过头想一想,为什么 macOS,iOS 还引入了黑暗模式,Chrome、Gmail 等主流应用还提供支持黑暗模式的特性? 其背后还是有着一定的思考空间:
- 护眼!没错,就是传说中的护眼。在夜晚,能够让用户的眼睛较为舒适,提高可视性
- 可大幅减少耗电量(具体取决于设备的屏幕技术)
- 一些特殊场景下,提高用户的体验度(小说阅读,视频观看)
通过 CSS 来支持黑暗模式
如果仅仅想针对黑暗模式,来更改网站的配色,那么 CSS 是一个不错的方法,前提是 系统开启了黑暗模式。
在 CSS 文件中,写入以下媒体查询代码:
css
@media (prefers-color-scheme: dark) {/* 黑暗模式下的样式代码 */}
css
@media (prefers-color-scheme: dark) {/* 黑暗模式下的样式代码 */}
当系统开启黑暗模式后,媒体查询内的样式就会默认生效。
PC 端实践
创建一个 HTML 文件,代码如下:
html
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><style>@media (prefers-color-scheme: dark) {p {color: red;}}</style><body><p>我在黑暗模式下会变红色</p></body></html>
html
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><style>@media (prefers-color-scheme: dark) {p {color: red;}}</style><body><p>我在黑暗模式下会变红色</p></body></html>
打开浏览器,显示字是黑色的,没什么问题,接下来让我们对系统开启黑暗模式。
这里以 windows 10 为例,我们只需在 开始-设置-个性化-颜色-选择默认应用模式(暗)。
再次打开浏览器,我们的样式已经成功生效!
苹果电脑用户,从 macOS Mojava 版本起,也可开启黑暗模式。
移动端实践
我们以 iPhone 为例,代码不变,只需在 设置-显示与亮度 开启黑暗模式即可,效果如图:
不足之处
根据 CanIUse.com(可以查询各浏览器的 CSS 属性支持情况)的数据显示:市面上的浏览器对该特性的支持率是 80%,IE 和 非 Chromium 内核版本的 Edge 不支持。所以如果你的网站面向 C 端,用户的浏览器各式各样,一定注意要做兼容处理。
并且用户无法在浏览器上主动地切换模式,只能被动依赖于系统的主题模式。
使用 JavaScript 切换样式表
使用 JavaScript 切换样式表来实现黑暗模式,我们需要创建两个不同的样式表,对应不同的主题(light and dark)。
第一步,在 head 标签中插入默认样式表:
html
<head>...<link id="theme" rel="stylesheet" type="text/css" href="light-theme.css" /></head>
html
<head>...<link id="theme" rel="stylesheet" type="text/css" href="light-theme.css" /></head>
然后,创建一个按钮来切换样式表,为了能让用户快速地找到,应尽可能置于网页的头部位置。
html
<button id="theme-toggle">Switch to dark mode</button>
html
<button id="theme-toggle">Switch to dark mode</button>
继续添加以下 JavaScript 代码段:
js
// 当 DOM 加载完成后触发回调函数document.addEventListener("DOMContentLoaded", () => {const themeStylesheet = document.getElementById("theme");const themeToggle = document.getElementById("theme-toggle");themeToggle.addEventListener("click", () => {// if it's light -> go darkif (themeStylesheet.href.includes("light")) {themeStylesheet.href = "dark-theme.css";themeToggle.innerText = "Switch to light mode";} else {// if it's dark -> go lightthemeStylesheet.href = "light-theme.css";themeToggle.innerText = "Switch to dark mode";}});});
js
// 当 DOM 加载完成后触发回调函数document.addEventListener("DOMContentLoaded", () => {const themeStylesheet = document.getElementById("theme");const themeToggle = document.getElementById("theme-toggle");themeToggle.addEventListener("click", () => {// if it's light -> go darkif (themeStylesheet.href.includes("light")) {themeStylesheet.href = "dark-theme.css";themeToggle.innerText = "Switch to light mode";} else {// if it's dark -> go lightthemeStylesheet.href = "light-theme.css";themeToggle.innerText = "Switch to dark mode";}});});
大家可以自行实践,这里就不展示了,那有没有优化的空间呢?
有!试想当用户切换到黑暗模式后,随后关闭了网站,当他们第二次访问时,又变成了默认主题,需要再次手动切换。不过,我们可以用 LocalStorage 来快速解决上述问题。
在 localStorage 中保存用户的选择
LocalStorage 能存储键值对,如下所示:
js
localStorage.setItem("theme", "dark-theme.css");
js
localStorage.setItem("theme", "dark-theme.css");
让我们对之前的代码做出一些优化:
js
// 当 DOM 加载完成后触发回调函数document.addEventListener("DOMContentLoaded", () => {const themeStylesheet = document.getElementById("theme");const storedTheme = localStorage.getItem("theme");if (storedTheme) {themeStylesheet.href = storedTheme;}const themeToggle = document.getElementById("theme-toggle");themeToggle.addEventListener("click", () => {// if it's light -> go darkif (themeStylesheet.href.includes("light")) {themeStylesheet.href = "dark-theme.css";themeToggle.innerText = "Switch to light mode";} else {// if it's dark -> go lightthemeStylesheet.href = "light-theme.css";themeToggle.innerText = "Switch to dark mode";}// 保存用户选择的主题localStorage.setItem("theme", themeStylesheet.href);});});
js
// 当 DOM 加载完成后触发回调函数document.addEventListener("DOMContentLoaded", () => {const themeStylesheet = document.getElementById("theme");const storedTheme = localStorage.getItem("theme");if (storedTheme) {themeStylesheet.href = storedTheme;}const themeToggle = document.getElementById("theme-toggle");themeToggle.addEventListener("click", () => {// if it's light -> go darkif (themeStylesheet.href.includes("light")) {themeStylesheet.href = "dark-theme.css";themeToggle.innerText = "Switch to light mode";} else {// if it's dark -> go lightthemeStylesheet.href = "light-theme.css";themeToggle.innerText = "Switch to dark mode";}// 保存用户选择的主题localStorage.setItem("theme", themeStylesheet.href);});});
这里不使用 sessionStorage 是因为 sessionStorage 的生命周期只存在于该域名下的标签页中,当标签页或浏览器关闭时,sessionStorage 会被清空,而 localStorage 不会,除非用户主动删除。
题外话,大家知道 localStorage 的最大容量是多少吗?当超出最大容量,又会发生什么?有什么解决方案吗?
需要注意的是,localStorage 严格遵守 同源策略,你在通过 HTTP 访问站点时保存的主题,将会在通过 HTTPS 访问站点时消失。
使用 JavaScript 切换类名
如果,我们只想用一个样式表,同样可以做到黑暗模式的切换。
添加以下 JavaScript 代码段:
js
button.addEventListener("click", () => {document.body.classList.toggle("dark");localStorage.setItem("theme",document.body.classList.contains("dark") ? "dark" : "light");});if (localStorage.getItem("theme") === "dark") {document.body.classList.add("dark");}
js
button.addEventListener("click", () => {document.body.classList.toggle("dark");localStorage.setItem("theme",document.body.classList.contains("dark") ? "dark" : "light");});if (localStorage.getItem("theme") === "dark") {document.body.classList.add("dark");}
CSS 文件,如下:
css
/* Light mode */body {background: #fff;color: #000;}/* Dark mode */body.dark {background: #000;color: #fff;}
css
/* Light mode */body {background: #fff;color: #000;}/* Dark mode */body.dark {background: #000;color: #fff;}
我们知道,CSS 样式的优先级取决于其选择器的权重叠加,哪个权重较大,就展示相应的样式:
(body = 1) < (body.dark = 1 + 10 = 11)
!important > 行内样式 > ID 选择器 > Class、伪类、属性选择器 > 元素、伪元素选择器
参考资料: