我的名字是弗拉基米尔,我为Yandex Mail开发移动前端。我们的应用程序暂时有一个黑暗的主题,但它不完整:只有界面和普通的电子邮件是黑暗的。具有自定义格式的消息仍然很轻,并且在黑暗的界面上脱颖而出,在晚上伤害了用户的眼睛。
今天我会告诉你我们是如何解决这个问题的。您将了解两种对我们不起作用的简单技术以及最终实现这一技巧的方法 - 自适应页面重新着色。我还将分享一些关于使图像适应黑暗主题的想法。公平地说,使用自定义CSS的页面变暗是一项相当特殊的任务,但我相信你们中的一些人可能会发现我们的经验很有帮助。
简单的方法
在确定我们的魔术颜色弯曲技术之前,我们尝试了两个非常基本的选项:对元素应用额外的深色样式或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。使用
fill
而不是background
,而stroke
不是color
,但你永远不能确定,它可能是相反的方式。
从技术角度来看,这是一个可靠的方法:它需要三行代码(好的,30个生产就绪版本,所有边缘案例都需要处理),它与世界上所有的浏览器兼容,它适用于开箱即用的动态页面,CSS附加到文档的方式完全无关紧要。还有一个很好的奖励:您可以轻松调整样式中的颜色以匹配应用程序的主要颜色(例如,使背景#bbbbb8
而不是黑色)。
顺便说一句,这就是我们以前用来变暗电子邮件的方式。如果一封电子邮件有自己的任何风格,我们只是放弃并保留它以防万一。
使用CSS过滤器
这是一种聪明而优雅的方法。您可以使用CSS过滤器使页面变暗:
.message--dark {
filter: invert(100) hue-rotate(180deg); /* hue-rotate возвращает тона обратно */
}
图像变得令人毛骨悚然,但我们可以轻松解决这个问题:
.message-dark img {
filter: invert(100) hue-rotate(180deg);
}
但是,这并没有解决通过background属性附加的内容图像的问题(当然,这对于调整宽高比很方便,但是语义呢?)。好吧,让我们假设我们可以找到所有这些元素,明确标记它们,并改变它们的颜色。
这种方法的好处是它保留了原始的亮度和对比度平衡。另一方面,它充满了超越优势的问题:
- 黑暗的页面变得明亮。
- 你无法控制最终的颜色。您对背景应用什么过滤器以使其与您的品牌相匹配
#bbbbb8
?这真是令人头疼的问题。 - 双反转使图像褪色。
- 性能低迷,尤其是在移动设备上 - 有意义的是,浏览器不是简单地渲染页面,而是在每次抽奖时运行傅里叶变换。
这种方法适用于带有中性色文本的电子邮件,但我从来没有见过任何人都有这样的内容的收件箱。另一方面,过滤器是在不访问其内容(帧,Web组件或图像)的情况下使元素变暗的唯一方法。
自适应黑暗主题
而现在是魔术的时候了!基于前两种方法的缺点,我们制定了一份清单,说明我们应该注意什么:
- 使背景变暗,文本灯和边框介于两者之间。
- 检测暗页并保留其颜色。
- 保持亮度和对比度的原始平衡。
- 控制结果颜色。
- 保持色调不变。
因此,您需要更改样式的颜色以使背景变暗。为什么不这样做,字面意思?把所有的款式,提取颜色相关规则(color
,background
,border
,box-shadow
,和它们的子属性),并与他们的“黑暗”的版本替换它们:变暗的背景和边框(但不如背景),减轻文本等
这种方法具有令人难以置信的优势,可以温暖任何开发人员的心。您可以为每个属性配置颜色转换规则 - 是的,使用代码!通过一点想象力,您可以将颜色适合任何外部主题,执行您想要的任何颜色校正(例如,做一个浅色主题而不是暗主题,或任何你喜欢的颜色的主题),甚至添加一个一点点语境 - 例如,对待宽窄边界的方式不同。
缺点是您对“一切都在js”方法的期望。我们运行脚本,搞乱样式封装,并用正则表达式解析CSS。然而,后者并不像HTML一样令人羞辱,因为CSS语法是常规的(至少在我们正在使用的级别)。
这是我们的黑暗计划:
- 规范化旧样式属性(
bgcolor
和其他属性)并将其移动到style="..."
。 - 找到所有内联样式。
- 查找各具风格的所有颜色相关的规则(
background-color
,color
,box-shadow
,等)。 - 从颜色相关属性中获取颜色,并将其与正确的转换器匹配(使背景变暗,使文本变亮)。
- 打电话给转换器。
- 将转换后的属性放回CSS中。
包装器代码 - 规范化,定位样式,解析 - 非常简单。让我们看看我们的魔术转换器究竟是如何工作的。
HSL转换
使颜色变暗并不像听起来那么简单,特别是如果你想保留色调(例如,将蓝色变成深蓝色而不是橙色)。你可以用普通的RGB做到这一点,但这是有问题的。如果您正在进行算法设计,您就会知道在RGB中,即使是渐变也会被破坏。同时,使用HSL中的颜色是一种真正的享受:而不是红色,绿色和蓝色,你不知道如何处理,你有以下三个渠道:
- 顺化 - 我们希望保留的东西。
- 饱和度,这对我们来说现在不是很重要。
- 轻盈,我们将改变。
你可以把它想象成一个圆柱体。我们需要颠倒这个圆筒。色彩校正做的事情如下:(h, s, l) => [h, s, 1-l]
。
颜色已经好了
有时我们很幸运,电子邮件的定制设计(或其中的一部分)已经很黑了。这意味着我们不需要改变任何东西 - 设计师在选择颜色方面可能比我们的算法所能想到的更好。在HSL中,您只需要考虑L - 亮度。如果它的值(或分别为文本和背景)高于或低于阈值(您可以设置),请不要管它。
动态马戏团
即使我们不需要这些(非常感谢,消毒剂,你在这里节省了我的一天),我将列出自适应主题需要的额外功能,以使整个页面变暗,而不仅仅是来自90年代的静态电子邮件。公平警告:这项任务并不适合所有人。
动态内联样式
更改内联样式是最简单的情况,会弄乱我们的黑暗页面。这很常见,但有一个简单的解决方法:MutationObserver
一旦发生更改,就添加并修复内联样式。
外部风格
<link>
由于异步性和@import
陈述,使用页面内部元素引用的样式可能会令人痛苦。CORS并没有让事情变得更好。看起来这个问题可以通过web worker(代理所有*.css
)非常优雅地解决。
动态风格
更糟糕的是,脚本可以添加,删除或重新排列<style>
和<link>
要素按照自己的意愿,甚至改变规则<style>
。在这里您还需要使用MutationObserver
,但每次更改都会产生更多处理。
CSS变量
当CSS变量发挥作用时,事情变得非常疯狂。你不能使变量变暗:即使你猜测变量包含基于其格式的颜色(虽然我会反对这一点),你仍然不知道它的作用是什么 - 背景,文本,边框或所有立刻?此外,CSS变量的值是继承的,因此您不仅需要跟踪样式,还要跟踪它们应用的元素,并且这很快就会升级失控。
一旦CSS变量成为主流,我们就遇到了麻烦。另一方面,color()
将得到支持,允许我们用a替换所有的JS转换color(var(--bg) lightness(-50%))
。
摘要
在我们的例子中,当消毒剂仅留下内联样式时,CSS级别的自适应变暗就像一个魅力:它提供更高质量的变暗,不会破坏原始结构,并且相对简单和快速。尽管如此,将其扩展到处理动态页面可能并不值得。幸运的是,如果您正在使用用户生成的内容并且您没有开发浏览器,那么您的清洁剂可能会以相同的方式运行。
在实践中,自适应模式应与样式覆盖配对,因为标准元素喜欢<input>
或<a>
经常使用默认的灯光样式。
如何使图像变暗
图像变暗是一个让我个人困扰的独立问题。这很有挑战性,并且给了我最后使用“光谱分析”这个短语的借口。黑暗主题中的图像存在几个典型问题。
有些图像太亮了。这些给我们带来了与我们开始追求的明亮电子邮件相同的麻烦。这个问题经常出现在普通照片中,但并不是这些照片所独有的。由于编写简报CSS并不是很有趣,有些人喜欢通过将复杂的布局作为图像插入来跳过角落,从而防止变暗。当我看到这一点时,我内心的完美主义者沮丧地呻吟着。除非您想吓唬用户,否则这些图像应该是暗淡的,但不能反转。
然后是真实透明的黑暗图像。这是徽标的典型问题 - 它们在设计时考虑了浅色背景,当我们用深色背景替换它时,徽标会消失在其中。这些图像需要反转。
介于两者之间的是使用白色背景的图像,该白色背景应该代表透明度。在黑暗的主题中,他们只是以一个奇怪的白色三角形结束。在一个完美的世界中,我们可以将白色背景更改为透明的,但如果您曾经使用过魔棒工具,那么您就知道自动执行此操作有多难。
有趣的是,有些图像没有任何意义。这些通常是跟踪像素或“格式持有者”,特别是古怪的布局。你可以安全地使它们不可见(比方说opacity: 0
)。
分析内在的东西
为了弄清楚如何为暗主题调整图像,我们需要查看内部并分析其内容,最好使用快速简单的方法。这是解决此问题的算法的第一个版本。
首先计算图像的暗,亮和透明像素。作为一种明显的优化,只需要考虑一小部分像素。接下来,确定图像的整体亮度(浅色,深色或中等)以及是否有透明度。反转深色图像,透明,暗淡不透明的明亮图像,并保持其余不变。
在我看到一张非洲学校的课程照片的慈善通讯之前,我对这一发现感到非常高兴。设计师通过在侧面添加透明像素来集中它。我们不想参与有关冒犯性图像识别的故事,因此我们决定将图像处理从第一次迭代中移除。
在未来,我们可以通过使用启发式算法“光谱分析”来防止这种问题。也就是说,计算图像中不同色调的数量,并且只有在没有太多色调时才反转。您也可以使用相同的方法识别明亮的粗略图像并将其反转。
结论
为了设计一个完整的黑暗主题,我们必须找到一种方法来使包含自定义格式的电子邮件变暗 - 我们做到了。首先,我们尝试了两种简单,纯粹的CSS解决方案 - 覆盖样式和使用CSS过滤器。它们都没有达到标准:第一个在原始设计上太难了,而另一个根本不能很好地工作。最后,我们决定使用自适应变暗。我们将风格分开,更换颜色,然后将它们重新组合在一起。我们目前正致力于将黑暗主题应用于图像。要做到这一点,我们需要分析它们的内容,然后只调整它们中的一些。
如果您需要更改自定义HTML snipets的颜色以使其适合黑暗主题,请记住以下三种方法:
- 覆盖样式。这是一个没有大惊小怪,没有muss方法,而且无论如何你还需要它。坏消息是它破坏了所有原始颜色。
- CSS过滤器。这很有趣,但还有很多不足之处。为不易访问的元素(如框架或Web组件)保留它。
- 风格改写。这种方法在变暗方面做得很好,但它更复杂。
即使你从来没有尝试过这些,我希望你读这篇文章的时间很棒!
via https://habr.com/en/company/yandex/blog/450032/