“像白天不懂夜的黑,像永恒燃烧的太阳,不懂那月亮的盈缺。” —— 黄桂兰
0x00 大纲
- 0x00 大纲
- 0x01 前言
- 0x02 CSS 自定义属性
- 0x03 主题定义
- 0x04 夜间模式
- 0x05 图片的处理
- 0x06 最后的拼图
- 0x07 小结
0x01 前言
夜间模式(Dark Mode),也被称为黑暗模式或深色模式,是一种高对比度,或者反色模式的显示模式,这种模式现在越来越流行,因为和传统的白底黑字相比,这种黑底白字的模式通常被认为可以缓解眼疲劳,更易于阅读。通过降低屏幕整体的亮度和使用暗色系的颜色,从而减小对眼睛的刺激。需要注意的是,夜间模式虽然能缓解视觉疲劳但并不能保护你的视力——该近视的还是会近视,该失眠的还是会失眠。
无论是 APP 还是网页,它们的实现原理都是相同的,本质上都是颜色和样式的替换,也就是主题的替换。下面主要以网页的夜间模式为例进行讨论。
通常一个网页的上的元素都有各自的颜色,例如文字的颜色,背景色,按钮和边框的颜色都有自己的单元设计,共同组合构成了一个整体的主题(配色方案)。一个网站可以包含相当多的 CSS, CSS 文件中的许多值都是重复数据,更改这些颜色可能很困难且容易出错,因为它(大概率)会分散在多个 CSS 文件中,并且可能不接受查找和替换。这将会变成开发人员的一个沉重负担,即使是 Ctrl+C,Ctrl+V 。
0x02 CSS 自定义属性
好在 CSS 规范里面有自定义属性(custom properties)的存在,它包含的值可以在整个文档中重复使用。通过自定义属性与var()
函数的结合,可以达到一次更改,处处生效的效果。
通常的最佳实践是定义在根伪类:root
下,这样就可以在 HTML 文档的任何地方访问到它了。声明一个自定义属性,属性名需要以两个减号(--
)开始,属性值则可以是任何有效的 CSS 值。
0x03 主题定义
首先以 CSS 自定义属性的方式定义一套主题颜色作为我们的起点。
<!DOCTYPE html>
<html mode="light">
<head>
<meta charset="utf-8">
<style>
:root[mode="light"] {
--bg-color: #ffffff;
--text-color: #596172;
--border-color: #efafc7;
}
div {
background-color: var(--bg-color);
color: var(--text-color);
border: 4px solid var(--border-color);
width: 200px;
height: 200px;
}
</style>
</head>
<body>
<div>Light or Dark.</div>
</body>
</html>
运行之后,应该得到这样的效果:
注意
如果你没有看到主题颜色生效,很可能是因为你的浏览器不支持 CSS 自定义属性,请更换现代浏览器或者点击这里查看更多关于兼容性的信息。
0x04 夜间模式
我们在上面的主题基础上,增加一套夜间模式的样式定义,同时增加一个按钮,用来切换我们的主题样式:
<!DOCTYPE html>
<html mode="light">
<head>
<meta charset="utf-8">
<style>
:root[mode="light"] {
--bg-color: #ffffff;
--text-color: #596172;
--border-color: #efafc7;
}
:root[mode="dark"] {
--bg-color: #202020;
--text-color: #d8d8d8;
--border-color: #d15900;
}
div {
background-color: var(--bg-color);
color: var(--text-color);
border: 4px solid var(--border-color);
width: 200px;
height: 200px;
}
</style>
<script>
function sw() {
var map = {'dark': 'light', 'light': 'dark'};
var current = document.querySelector('html').getAttribute('mode');
document.querySelector('html').setAttribute('mode', map[current]);
}
</script>
</head>
<body>
<div>Light Mode for Light.</div>
<button onclick="sw()">切换</button>
</body>
</html>
点击按钮,应该能看到切换后的效果:
这只是一个简单的示例,事实上,你可以细化和定义任何你想要的颜色,甚至是 SVG 等矢量图元的颜色。
0x05 图片的处理
一张白底黑字的图片,在晚上看起来,就像是在直视一盏台灯。通过 CSS 自定义属性,我们已经完成了对各种页面元素颜色的控制和改变,但是对于颜色既定的图片(自带背景色的图片)来说,似乎就不怎么行得通了。既然预处理行不通,就要求助于后处理了,没错,说的就是滤镜(filter)。好在,滤镜也是可以通过 CSS 自定义属性定义的。
<!DOCTYPE html>
<html mode="dark">
<head>
<meta charset="utf-8">
<style>
div[mode="light"] {
--img-filter: brightness(1.0);
}
div[mode="dark"] {
--img-filter: brightness(.7);
}
img {
filter: var(--img-filter);
}
</style>
</head>
<body>
<div mode="light">
<img src="">
</div>
<div mode="dark">
<img src="">
</div>
</body>
</html>
运行后的效果如下,左边是原图,对应我们的正常模式,右边是滤镜处理后的图片,对应我们的夜间模式:
可以看到图片的整体亮度被降低,亮部不再显得那么刺眼,暗部则变得更暗。我们达到所需要的效果,但同时需要注意,它降低的是图片中所有颜色分量的亮度,因此,如果滤镜参数调得太低,将会导致原图中部分细节的丢失或者使其上面的文字变得难易阅读(这里要吐槽下知乎的夜间模式)。个人建议的设置是保留70%~90%的亮度水平。
0x06 最后的拼图
我们在上文处理了夜间模式的文字和图片等文档元素,但这样真的完美了吗?事实上并不。在图片的处理中我们提到,可以通过降低图片的整体亮度来进行柔化处理,使其在夜间显得没那么刺眼。这种处理暗含一个条件就是,原始图片的前景和背景本身是相对融洽的。
考虑有这样一张图片,它的内容是黑色的,背景是透明的。当它在背景色为白色的页面上显示,似乎效果很好。但当我们切换到夜间模式,此时页面背景色变为黑色系,这张图片显然已经难以看清,极端情况下,当页面的背景色和图片的前景色相同时,这张图片就像凭空消失了一样。譬如下图的二维码:
解决这个问题的方法有很多,但我暂时没有想到一个通用且完美的办法:
- 设置两套不同的图片(成本巨大)
- 不使用透明的图片(某些情况下不现实)
- 特殊问题特殊处理,给透明图片赋予与全局背景色不同的背景颜色(适用于透明图片不多的情况)
在探索这个问题的途中,倒是发现了另一种有趣的处理方法:
<!DOCTYPE html>
<html mode="dark">
<head>
<meta charset="utf-8">
<style>
div[mode="light"] {
--bg-color: #ffffff;
--img-filter: brightness(1.0);
}
div[mode="dark"] {
--bg-color: #090909;
--img-filter: brightness(.7);
}
div {
background-color: var(--bg-color);
}
img {
filter: var(--img-filter);
}
.special {
filter: var(--img-filter) invert(1);
}
</style>
</head>
<body>
<div mode="light">
<img src="">
</div>
<div mode="dark">
<img src="">
</div>
<div mode="dark">
<img src="" class="special">
</div>
</body>
</html>
这个方法适用于黑白图片或者内容与颜色无强关联的图片,运行上面的代码,我们会得到这样的效果:
最右边的图片使用了完全翻转滤镜,也就是将黑的变白了,白的变黑了。这样在白色背景下,我们看到的是正常的二维码,在黑色背景下,看到的是反转后的二维码,并且还不会影响扫码。
0x07 小结
我们通过 CSS 自定义属性和滤镜等特性完成了对夜间模式样式的处理和优化,但是仍要注意在特定的场景下,会有混色的问题出现。如果混色的问题,最好的办法还是特殊问题特殊处理,根据实际情况修改源图或者使用翻转滤镜。暂时没有完美的解决方案,期待随着技术的发展,后续的 CSS 新特性能带来新的工具和惊喜。