如何创造一个黑暗的主题而不是伤害。 Yandex.Mail团队经验


我叫弗拉基米尔(Vladimir),我从事Yandex.Mail的移动前端。 在我们的应用程序中,已经有一个黑暗的话题,但是还不够:我们能够重绘界面和简单的字母。 但是格式化后的字母仍然保持浅色,并与深色界面形成对比,这可能会使我的眼睛在晚上疲倦。


今天,我将告诉哈勃(Habr)的读者我们如何解决这个问题。 您将了解不适合我们的两种简单方法,然后-了解页面自适应重绘的主要方法,最后了解下一次迭代的方向:重绘图片。 尽管任务本身(用任意格式重新粉刷页面)是特定的,但我认为我们的经验也将对您有所帮助。


简单的方法


在使用神奇的“ repainter”之前,我们尝试了两个简单的选择,例如软木塞:在元素上悬挂一个额外的深色样式或CSS过滤器。 他们不适合我们,但也许在某些情况下会更好(因为这很酷)。


替代样式


最简单的方法是,以逻辑方式在CSS中扩展应用程序本身的深色主题:我们将深色样式悬挂在用于容纳字母的容器上(通常情况下,对于需要重绘其他人的内容):


.message--dark { background-color: black; color: white; } 

但是,如果字母中的元素具有自己的样式,它们将重新定义我们的根样式。 不!important无济于事。 您可以通过砍掉继承来挤压一个想法:


 .message--dark * { background-color: black !important; color: white !important; border-color: #333 !important; } 

在这种情况下,您不能没有!important ,因为选择器本身不是很具体。 此外,将需要重新定义内联样式(以及带有!important内联样式无论如何都会爬行​​,没有什么可做的)。


我们的风格相当笨拙,并且所有颜色都相同,因此又出现了另一个问题:也许设计师想通过排列颜色(元素优先级和其他设计师的东西)来表达一些想法,但我们还是放弃了整个想法。



如果您比我更尊重设计师,并且仍然决定使用此方法,请不要忘记完成一些不明显的琐事:


  • box-shadow -只有颜色无法重新定义;您必须删除所有阴影或使用浅色阴影。
  • 语义元素的颜色-链接,输入元素。
  • 内联SVG-而不是background他们需要设置fill ,而不是color - stroke ,但这并不准确,具体取决于哪种SVG-可能相反,反之亦然。

从技术上讲,该方法还不错:这是三行代码(好的,对于带重整情况的生产redi版本来说是三十行),与世界上所有浏览器兼容,开箱即用地处理动态页面,并且不绑定原始文档中样式的连接方式。 一个特别的好处是,您可以轻松地调整样式中的颜色,以使其适合主要应用程序(例如,使背景#bbbbb8而不是黑色)。


顺便说一句,我们曾经用这种方式重绘字母,但是如果我们发现字母内部有任何样式,我们就会感到害怕,并让字母亮起来。


CSS过滤器


非常机智和优雅的选择。 您可以使用CSS过滤器重新绘制页面:


 .message--dark { filter: invert(100) hue-rotate(180deg); /* hue-rotate    */ } 

此后,照片将变得令人毛骨悚然,但这没关系-我们将其重新粉刷:


 .message-dark img { filter: invert(100) hue-rotate(180deg); } 


内容图像通过background绑定仍然存在问题(我们知道调整宽高比更方便,但是语义呢?)。 假设我们可以找到所有这些元素,对其进行显式标记并重新粉刷它们。


该方法的优点在于它保留了亮度和对比度的原始比率。 另一方面,存在很多问题,而它们的好处远胜于它们:



  1. 深色页面变浅。
  2. 最终的颜色无法控制-哪个滤镜可用于微调公司#bbbbb8的背景? 谜语。
  3. 两次重新粉刷后,图片褪色。
  4. 一切都变慢了(尤其是在手机上)-这是合乎逻辑的,现在,浏览器不再需要简单的渲染,而是需要在每个屏幕上驱动图像处理。

此方法适用于由中性色调的文字组成的字母,但是谁能获得完整的具有这种特殊内容的美感器呢? 但是过滤器可以重新绘制其内容不可访问的元素-框架,Web组件,图片。


响应主题


现在是魔术的时候了! 从前两种方法的缺点中,我们收集了一个清单:


  1. 使背景变暗,文本变浅,边框变中。
  2. 识别已经暗的页面,不要重新粉刷它们。
  3. 保持原始的亮度和对比度。
  4. 提供自定义颜色的功能。
  5. 保留开始时的音调。

我们需要更改样式的颜色,以使背景变暗。 为什么不按字面意思做呢? 我们只需要采用所有样式,查找与颜色( colorbackgroundborderbox-shadow ,它们的子属性)相关的规则,然后将其替换为“变黑”-变暗背景,变浅文本,使边界变暗小于背景,等等。


这种方法具有一个令人难以置信的优势,它将温暖任何开发人员的灵魂。 可以配置每个属性(是的,直接用代码来描述!)它自己的颜色转换规则。 有了足够的想象力,您就可以与任何外部主题集成,进行任何颜色校正(例如,使颜色为浅色或灰棕色覆盆子而不是深色主题),甚至还可以添加一些上下文,例如以不同的方式处理宽边框和窄边框。


缺点是js中所有内容的标准配置。 是的,我们运行脚本,破坏样式的封装并解析CSS regexp。 嗯,与HTML不同,后者并不是那么可耻,因为CSS语法(我们需要的水平)仍然是常规的。


重新粉刷计划如下:


  1. 我们对样式的旧属性( bgcolor和friends)进行规范化,然后将其转移为style="..."
  2. 查找所有内联样式。
  3. 在每种样式中,我们都找到所有颜色规则( background-colorcolorbox-shadow等)。
  4. 从所有颜色规则中,我们得到了颜色,我们找到了所需的转换器(背景为暗淡,文本为澄清)。
  5. 我们称转换器。
  6. 将转换后的规则放回CSS中。

绑定(规范化,样式搜索,解析)非常简单。 我们将弄清楚魔术转换器的工作原理。


HSL转换


“使颜色变暗”并不像看起来那样简单,特别是如果我们要保持色调(青色变为深蓝色,而不是橙色)时。 这可以在普通RGB中完成,但存在问题。 算法设计的爱好者知道,即使是那里的渐变也是弯曲的。 但是在HSL中使用颜色是一种纯粹的乐趣:除了不清楚要做什么的红色,绿色和蓝色外,我们还有其他三种渠道:


  • 色调只是我们想要保留的基调。
  • 饱和度-饱和度,这对我们现在不是很重要。
  • 亮度-我们将改变的亮度。

方便地以圆柱体的形式想象这样的空间。 我们的任务是将圆柱体倒置。 颜色分级功能的作用类似于(h, s, l) => [h, s, 1 - l]


一切都很好的颜色


有时情况是成功的:字母(或其一部分)的专有设计已经很暗。 在这种情况下,您无需进行任何更改,最好保持安静,最好-设计师选择的颜色不会比我们的算法差。 在HSL中,只需查看L-亮度。 如果阈值较高(对于文本)或较低(对于背景)(当然是可自定义的),则我们什么也不做。


动态马戏团


尽管我们不需要它(再次感谢,消毒剂,您使我免于疯狂!),但我还是会告诉您自适应主题需要什么样的附件来使整页变暗,而不仅仅是90年代的愚蠢静态信件。 更准确地说,这对于喜欢早晨选择器气味的人来说是一项任务。


动态内联样式


使我们的页面变暗的最简单情况是更改内联样式。 该操作很频繁,但是修复很简单:添加MutationObserver并在更改时快速修复内联样式。


外部风格


由于异步和@import ,从页面内部使用<link>样式工作非常痛苦,而CORS不再有趣。 似乎可以通过网络工作者( *.css代理)很好地解决此问题。


动态风格


最后,将所有问题汇总在一起,我们记得该脚本通常可以添加,删除和重新排列(特殊性!级联!) <style><link> ,甚至可以更改<style>的规则。 一切都由同一个MutationObserver解决,用于样式元素,但每次更改都会有更多处理。


CSS变量


当CSS变量进入游戏时,将会出现全新的疯狂局面。 我们不能掩盖变量本身:即使我们假设我们将根据变量包含颜色的格式来猜测(尽管我不建议您这样做),也不清楚它将以什么角色遇到我们-背景,文本,边框,一次全部出现? 而且,变量的值是继承的,因此我们不仅要考虑样式,还要考虑应用变量的元素,所有这些都会迅速升级和爆炸。


如果CSS变量成为主流,那我们就有问题了。 另一方面,到那时, color()已经启动,使用它可以不更改JS中的颜色,而只需用color(var(--bg) lightness(-50%))替换颜色即可。


总结



对于我们的情况,当消毒剂仅保留内联样式时,在CSS级别上的自适应调光效果很好:它提供了最佳的调光质量,不会打断字母,并且相对快速且容易地工作。 不知道所有带有动态填充的选项是否值得。 幸运的是,如果您使用用户生成的内容并且不编写浏览器,则您的清理程序也应该执行相同的操作。


在实践中,自适应模式应与样式的重新定义一起使用:样式通常不显式应用于诸如<input><a>类的标准元素,但默认情况下它们为浅色。


如何使图片变暗


重新粉刷图片是一个困扰我个人的独立问题。 这很有趣,我终于有机会使用“光谱分析”一词。 在黑暗的被摄对象中存在几个常见的问题。


首先,图片太亮。 它的工作方式与所有未上漆的字母相同。 这些照片通常(但不一定)是普通照片。 由于时事通讯的布局不是很有趣,所以许多人只是将信件的复杂部分导出为图片,而不会重新粉刷,并且在晚上阐明了我的完美主义。 此类图片需要变暗,但不能反转-否则会出现可怕的底片。



其次,具有真实透明度的深色图片。 此问题通常在徽标上发现-它们是为浅色背景设计的,当我们将其替换为深色背景时,请将其合并。 此类图片需要反转。



中间某处有一些图片,白色代表“透明背景”,但是现在它们只是站在一些奇怪的白色矩形中。 在理想的世界中,我们将白色背景替换为透明背景,但是如果您曾经在照片编辑器中使用过魔杖,那么您会知道自动执行此操作并不那么容易。



有趣的是,有时图片根本没有任何意义-它们是跟踪像素和特殊格式的“格式持有者”。 可以安全地使它们不可见(例如, opacity: 0 )。



内省启发法


要决定如何处理图片,我们需要深入并分析其内容-并以简单快速的方式进行。 根据我们的一系列问题,该算法的第一个版本迫在眉睫。 她在那儿。


我们考虑图片中的暗,亮和透明像素,而不是全部像素,而是选择性地-明显的优化。 我们确定图片的整体亮度(亮,暗,中)和透明度。 反转具有透明度的黑暗图像,没有透明度的光-静音,请勿触摸其余部分。


当我看到一本慈善通讯和一本非洲学校的课程照片时,这种奇妙的启发式游戏的乐趣就结束了。 一切都很好,但设计人员将其居中,并在边缘添加了透明像素。 我们不想让自己陷入有关冒犯性图片识别的新故事的中心,因此我们决定在第一版中根本不进行图像处理。


将来,我称之为“光谱分析”的另一种启发式方法应该可以防止此类问题的发生-我们计算图片中不同颜色的数量,并仅在颜色数量很少的情况下将其反转。 可以使用相同的标准来搜索图形光图片并对其重新粉刷-听起来很诱人。



总结


对于邮件中一个完整的深色主题,我们缺少带有样式的粉刷字母,因此我们想出了如何安排它。 纯CSS中的两个简单选项-重新定义样式和CSS过滤器-无效:第一个选项在原始设计上太难了,第二个效果不好。 结果,我们使用了自适应调光-我们解析样式,用更合适的颜色替换颜色,然后将它们收集回来。 现在,我们正在努力将主题扩展为图片-为此,我们需要分析其内容并仅重绘一些内容。


如果您需要将自定义HTML重新绘制为深色主题,请记住以下三种方法:


  • 覆盖样式-无论如何,主要和便宜的样式都需要它,但是它会杀死所有原始颜色。
  • CSS过滤器很酷,但是它可以正常工作。 仅用于不透明(就访问而言)元素,例如框架或Web组件。
  • 转换样式-使高质量变暗,但比其他方法复杂。

即使您从未这样做,也希望您玩得开心!


有用的链接


  1. 如果您有兴趣在Android的开发环境中热烈讨论这个主题,那么我们邀请您 4月18日访问 Yandex的Petersburg办公室。


  2. 最近,我们讨论了解决邮件用户的另一个问题-邮件问题。


Source: https://habr.com/ru/post/zh-CN446780/


All Articles