草庐IT

也及夜间模式样式

程语有云 2023-03-28 原文

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

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 新特性能带来新的工具和惊喜。

有关也及夜间模式样式的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 如何使用文字标量样式在 YAML 中转储字符串? - 2

    我有一大串格式化数据(例如JSON),我想使用Psychinruby​​同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解

  4. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  5. ruby - 如何在 Ruby 中拆分参数字符串 Bash 样式? - 2

    我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"

  6. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  7. ruby-on-rails - 添加回形针新样式不影响旧上传的图像 - 2

    我有带有Logo图像的公司模型has_attached_file:logo我用他们的Logo创建了许多公司。现在,我需要添加新样式has_attached_file:logo,:styles=>{:small=>"30x15>",:medium=>"155x85>"}我是否应该重新上传所有旧数据以重新生成新样式?我不这么认为……或者有什么rake任务可以重新生成样式吗? 最佳答案 参见Thumbnail-Generation.如果rake任务不适合你,你应该能够在控制台中使用一个片段来调用重新处理!关于相关公司

  8. ruby-on-rails - environment.rb 中设置的常量在开发模式中消失 - 2

    了解Rails缓存如何工作的人可以真正帮助我。这是嵌套在Rails::Initializer.runblock中的代码:config.after_initializedoSomeClass.const_set'SOME_CONST','SOME_VAL'end现在,如果我运行script/server并发出请求,一切都很好。然而,在我的Rails应用程序的第二个请求中,一切都因单元化常量错误而变得糟糕。在生产模式下,我可以成功发出第二个请求,这意味着常量仍然存在。我已通过将以上内容更改为以下内容来解决问题:config.after_initializedorequire'some_cl

  9. Ruby:标准递归模式 - 2

    我经常迷上ruby​​的一件事是递归模式。例如,假设我有一个数组,它可能包含无限深度的数组作为元素。所以,例如:my_array=[1,[2,3,[4,5,[6,7]]]]我想创建一个方法,可以将数组展平为[1,2,3,4,5,6,7]。我知道.flatten可以完成这项工作,但这个问题是作为我经常遇到的递归问题的一个例子-因此我试图找到一个更可重用的解决方案。简而言之-我猜这种事情有一个标准模式,但我想不出任何特别优雅的东西。任何想法表示赞赏 最佳答案 递归是一种方法,它不依赖于语言。您在编写算法时要考虑两种情况:再次调用函数的情

  10. ruby - 在 Ruby 中查找多个正则表达式匹配的模式和位置 - 2

    这应该是一个简单的问题,但我找不到任何相关信息。给定一个Ruby中的正则表达式,对于每个匹配项,我需要检索匹配的模式$1、$2,但我还需要匹配位置。我知道=~运算符为我提供了第一个匹配项的位置,而string.scan(/regex/)为我提供了所有匹配模式。如果可能,我需要在同一步骤中获得两个结果。 最佳答案 MatchDatastring.scan(regex)do$1#Patternatfirstposition$2#Patternatsecondposition$~.offset(1)#Startingandendingpo

随机推荐