对于操作系统 深色/浅色模式 自适应 的一些思考和实践
背景
最近在为一个网站做针对操作系统(比如windows11)的 深色模式/浅色模式 的适配,作为一个业余前端,之前并没有接触过这方面内容,经过一番搜索,得到可能有用的知识点如下:
- CSS: 媒体查询 prefers-color-scheme, 当操作系统发生 深色模式/浅色模式的转换时,能够实时切换
- CSS: 颜色变量
- Javascript: window.matchMedia("(prefers-color-scheme: light)") 通过注册监听事件的方式,也可以做到当操作系统发生 深色模式/浅色模式的转换时,能够实时响应
实现
根据以上技术点,我们可以按下面的步骤来实现对 深色模式/浅色模式 的适配:
抽离原CSS文件中的颜色色值,将其全部转换为变量的形式,并将其作为默认模式(浅色模式),例如:
/* 最好注意颜色语义的抽象,当语义相同且颜色相同时,仅使用一个颜色变量 */ :root { --body-bg-color: #FFF; --default--text-color: #333; --default-link-color: #4476A6; --default-link-hover-color: #4073A2; --default-border-color: #F2F2F0; } body { background: var(--body-bg-color); } a { color: var(--default-link-color); } a:hover { color: var(--default-link-hover-color); }
增加一个 Media Query 来适配 深色模式,其中的颜色变量暂时从默认的浅色模式中全部复制而来,后面再进行颜色的更改:
@media (prefers-color-scheme: dark) { :root { --body-bg-color: #FFF; --default--text-color: #333; --default-link-color: #4476A6; --default-link-hover-color: #4073A2; --default-border-color: #F2F2F0; } }
针对颜色变量列表中的黑白灰色系的颜色,经过简单运算,得到其对应的深色模式的颜色色值,并更新到深色模式中:
颜色的算法其实也比较简单,以上面的CSS代码为例,其浅色模式背景色为
#FFF
, 文字颜色为#333
。- 首先假定我们的深色模式背景色为
#202020
。 - 然后准备计算对应的深色模式中的文字颜色(当颜色色值的RGB位不相同时,需要针对RGB中的每一位单独计算):
- 浅色模式中背景颜色
#FFFFFF
对应的十进制颜色为 DEC(#FFFFFF) =>rgb(255, 255, 255)
- 浅色模式中文字颜色
#333333
对应的十进制颜色为 DEC(#333333) =>rgb(51, 51, 51)
- 可知,浅色模式中,文字颜色距离背景颜色相差: 255 - 51 = 204
- 深色模式中背景颜色
#202020
对应的十进制颜色为 DEC(#202020) =>rgb(32, 32, 32)
- 则,距离深色模式中背景颜色相差 204 的颜色为:(255 - 51) + 32 = 236, 转换为16进制, HEX(236) = EC
- 浅色模式中文字颜色为
#ECECEC
- 公式可总结为:深色文字色值 = HEX(DEC(浅色背景色值) - DEC(浅色文字色值) + DEC(深色背景色值))
- 浅色模式中背景颜色
根据上一步中的公式逐个计算出深色模式中对应的黑白灰系颜色的色值,并更新到
@media (prefers-color-scheme: dark)
中除黑白灰之外的其他颜色可能会包含特定的语义,比如红色代表错误,绿色代表成功,蓝色代表提示信息之类的,此种颜色需要根据自己的情况决定是否需要转换
更新完成后,深色模式的颜色变量列表为:
@media (prefers-color-scheme: dark) { :root { --body-bg-color: #202020; --default--text-color: #ECECEC; --default-link-color: #4476A6; --default-link-hover-color: #4073A2; --default-border-color: #2D2D2F; } }
- 首先假定我们的深色模式背景色为
对于CSS无法控制的一些地方,比如一些背景图片的
src
属性,则可以使用 javascript 进行变换:const scheme = window.matchMedia("(prefers-color-scheme: light)") var adaptSystemSchemeColor = function (light) { if (light.matches) { $('.some-pictures').each((i, item) => { item.src = item.src.replace('bg-dark.jpg', 'bg-light.jpg') }) } else { $('.some-pictures').each((i, item) => { item.src = item.src.replace('bg-light.jpg', 'bg-dark.jpg') }) } } // 注册监听,以便当操作系统发生 深色模式/浅色模式 的转换时,能够实时响应 scheme.addListener(e => { adaptSystemSchemeColor(e) }) // 第一次页面加载时,根据操作系统当前模式,调整图片src adaptSystemSchemeColor(scheme)
千万注意 input / textarea / select 等控件的背景色,当不指定背景色时,即使在深色模式下,背景色仍然会是白色。