您可能知道,CSS 的最新更新和补充功能非常强大。从 Flexbox 到 Grid,以及——我们这里关注的——自定义属性(又名 CSS 变量),所有这些都使构建健壮和动态的布局和界面比以往任何时候都更容易,同时还打开了我们过去只能梦寐以求的许多其他可能性。
前几天,我在想,一定有办法使用自定义属性为元素的背景着色,同时保持与前景颜色足够高的对比度(使用白色或黑色)以通过 WCAG AA 可访问性标准。
使用几行 JavaScript 代码来执行此操作非常高效
var rgb = [255, 0, 0];
function setForegroundColor() {
var sum = Math.round(((parseInt(rgb[0]) * 299) + (parseInt(rgb[1]) * 587) + (parseInt(rgb[2]) * 114)) / 1000);
return (sum > 128) ? 'black' : 'white';
}
这会获取元素背景颜色的红色、绿色和蓝色 (RGB) 值,分别将它们乘以 一些特殊数字(分别为 299、587 和 144),将它们加在一起,然后将总和除以 1,000。当该总和大于 128 时,它将返回黑色;否则,我们将获得白色。还不错。
唯一的问题是,当在 CSS 中重新创建此功能时,我们无法访问本机 if
语句来评估总和。那么,如何在没有 if
语句的情况下在 CSS 中复制此功能呢?
幸运的是,与 HTML 一样,CSS 也非常宽容。如果我们将大于 255 的值传递给 RGB 函数,它将被限制在 255。对于小于 0 的数字也是如此。即使是负整数也会被限制在 0。因此,我们无需测试总和是否大于或小于 128,而是从总和中减去 128,从而得到正整数或负整数。然后,如果我们将其乘以一个较大的负值(例如 -1,000),我们最终会得到非常大的正值或负值,然后可以将其传递给 RGB 函数。就像我之前说的,这将被限制在浏览器期望的值。
这是一个使用 CSS 变量的示例
:root {
--red: 28;
--green: 150;
--blue: 130;
--accessible-color: calc(
(
(
(
(var(--red) * 299) +
(var(--green) * 587) +
(var(--blue) * 114)
) / 1000
) - 128
) * -1000
);
}
.button {
color:
rgb(
var(--accessible-color),
var(--accessible-color),
var(--accessible-color)
);
background-color:
rgb(
var(--red),
var(--green),
var(--blue)
);
}
如果我的数学计算正确(也有可能不正确),我们将得到 16,758 的总和,这远大于 255。将此总和传递给所有三个值的 rgb()
函数,浏览器将把文本颜色设置为白色。
添加一些范围滑块来调整 color
值,就是这样:一个动态的 UI 元素,可以根据其 background-color
交换文本颜色,同时保持通过 WCAG AA 的评分。
查看 CodePen 上的示例
仅 CSS 可访问按钮,作者为 Josh Bader (@joshbader)
在 CodePen 上。
将此概念应用于实践
下面是一个 CodePen 示例,展示了如何使用此技术为用户界面设置主题。我已经复制并将 --accessible-color
变量移动到需要它的特定 CSS 规则中,并且为了帮助确保背景基于其前景保持可访问性,我在多个地方将 --accessible-color
变量乘以 -1。可以通过使用位于右下角的控件来更改颜色。单击齿轮/齿轮图标以访问它们。
查看 CodePen 上的示例
CSS 变量可访问 UI,作者为 Josh Bader (@joshbader)
在 CodePen 上。
还有其他方法可以做到这一点
不久前,Facundo Corradini 解释了如何在 这篇文章 中执行非常类似的操作。他结合使用了略微不同的计算方法和 hsl
函数。他还详细介绍了在提出此概念时遇到的一些问题
某些色调会变得非常成问题(尤其是黄色和青色),因为尽管它们具有相同的亮度值,但显示出的亮度却比其他色调(例如红色和蓝色)高得多。因此,某些颜色被视为暗色,并使用白色文本,尽管它们非常亮。
CSS 到底发生了什么?
他继续提到 Edge 没有限制他的大数字,并且在我的测试过程中,我注意到有时它可以工作,而其他时候则不行。如果有人能查明为什么会这样,请随时在评论中分享。
此外,Ana Tudor 解释了如何使用 filter
+ mix-blend-mode
可以 帮助文本与更复杂的背景形成对比。而且,当我提到复杂时,我的意思是复杂。她甚至展示了文本颜色如何在背景颜色的部分发生变化时发生变化——非常棒!
此外,Robin Rendle 解释了如何使用 mix-blend-mode
以及伪元素来 根据其 background-color
自动反转文本颜色。
因此,可以将此视为添加到混合中的另一种方法。自定义属性能够为我们打开这些可能性,同时允许我们以各种方式解决相同的问题,这真是太棒了。
非常巧妙
太棒了!我喜欢你制作的关于“将此概念应用于实践”的 CodePen 示例。
感谢这篇文章!你从哪里得到“特殊数字”的?
W3C 在此处列出了它们… https://www.w3.org/TR/AERT/#color-contrast
我不知道为什么,但在 Gecko 56 上不起作用
限制数字的问题可能与操作系统和硬件设置有关。颜色函数的实现方式很容易取决于浏览器是否可以访问硬件加速。过去,我曾 观察到“果冻效果”(在这种情况下为 alpha 值)的类似错误,尽管现在似乎已解决。
Josh,这是一个很酷的想法,但我担心你的数学计算不准确——无论是在 JavaScript 还是 CSS 中。你在那个 w3c 页面上对“特殊数字”的解释很差。实际上,红色、蓝色和绿色需要首先进行归一化(设置为 0 到 1 的范围),然后线性化(从 sRGB 转换为 linearRGB),然后再将它们传递到该公式中。许多实现中都缺少后一步,例如 Bootstrap 的
yiq()
Sass 函数,但它绝对是唯一正确的计算方法,并且有 w3c 和 WCAG 文档正确地引用了它。不幸的是,它需要一个pow()
函数,JavaScript 有,但 CSS 没有,因此使用calc()
对其进行精确模拟将不起作用。此 w3c 文档 正确地表明 RGB 值必须线性化。你之前链接的文档提到“此算法取自将 RGB 值转换为 YIQ 值的公式”,但没有解释 YIQ 颜色假设伽马值已经线性化。
根据 caniuse.com,–var() 仍然不是 IE11 的选项,我正在通过来自各种客户的愤怒的强烈反对来发现这一点,这些客户仍在使用……
好主意!我们如何将其转换为 SCSS?这将解决 IE11 的兼容性问题。
我实际上后来在 https://bootstrap.ac.cn/docs/4.3/getting-started/theming/#color-contrast 找到了 Bootstrap 的 YIQ 函数。我想这是相同的东西?
想法相同,但不幸的是数学上不正确 ♂️
Lu,这就是我的担忧。如果我们能够覆盖该函数以使其正常工作,那就太好了。
@Glenn
这个示例包含正确的数学公式,同时也包含了Bootstrap的yiq()函数作为对比。不过需要注意的是,这段代码依赖于Sass的
pow()
函数。我使用的是mathsass来提供这个函数,通过示例的依赖项(参见设置)。这个库很好用,但是它在SassScript中运行,所以速度比较慢。理想情况下,可以使用Sass的functions
Node API来插入一些JavaScript数学函数。如果感兴趣,我有一套简单的函数可以放到Gist上。哇,谢谢你的分享,Lu。我非常感兴趣。我认为无论解决方案是什么,都需要易于集成到新的项目中,否则我的同事不会使用它——他们不像我一样对这些东西感兴趣。
非常棒的工作!
不幸的是,MS Edge目前不支持使用css变量的calc() :/
必须等到可以用于生产环境时才能使用。
很棒的工作,除了有一点:你的颜色选择器滑块没有可访问的彩色文本指示滑块的值!因此,如果你将四级颜色(用于颜色选择器的背景)设置得很浅,你就无法看到滑块的实际数值。我在Chrome中尝试了这个功能,所以其他浏览器可能会有所不同;但是,如果你无法控制指示器文本的颜色,你可能需要考虑在滑块后面放置一个深色的背景。
如果你在*上而不是在:root上应用公式,使用通用的–red,–green,–blue变量,你就不必重复这个大公式了。然后,在任何你想使用它的地方,为该选择器设置通用的红色、绿色、蓝色变量。
这是代码示例的修改版本:https://codepen.io/joyously/pen/QWMdXXB