也及夜间模式样式

时间:2022-12-20 17:05:42

“像白天不懂夜的黑,像永恒燃烧的太阳,不懂那月亮的盈缺。” —— 黄桂兰

0x00 大纲

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 style="display: inline-block;" mode="light">
        <img src="sun.jpg">
    </div>
    <div style="display: inline-block;" mode="dark">
        <img src="sun.jpg">
    </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 style="display: inline-block;" mode="light">
        <img src="qr.png">
    </div>
    <div style="display: inline-block;" mode="dark">
        <img src="qr.png">
    </div>
    <div style="display: inline-block;" mode="dark">
        <img src="qr.png" class="special">
    </div>
</body>
</html>

这个方法适用于黑白图片或者内容与颜色无强关联的图片,运行上面的代码,我们会得到这样的效果:

也及夜间模式样式

最右边的图片使用了完全翻转滤镜,也就是将黑的变白了,白的变黑了。这样在白色背景下,我们看到的是正常的二维码,在黑色背景下,看到的是反转后的二维码,并且还不会影响扫码。

0x07 小结

我们通过 CSS 自定义属性和滤镜等特性完成了对夜间模式样式的处理和优化,但是仍要注意在特定的场景下,会有混色的问题出现。如果混色的问题,最好的办法还是特殊问题特殊处理,根据实际情况修改源图或者使用翻转滤镜。暂时没有完美的解决方案,期待随着技术的发展,后续的 CSS 新特性能带来新的工具和惊喜。