September 24, 2018
网页设计中,文本是最常用的元素之一,文本易读性非常重要,我们都希望页面更加清晰易读,而颜色在文本易读性中起到了至关重要的作用。 文本和背景颜色有一个“对比度”,了解并能正确调整这个对比度,将会让你的页面更加清晰易读,进而提高阅读效率和阅读体验。
在缺乏专业设计的页面中,我们经常见到下面这样的画面:
这是公司的某内部系统:
公司某内部工具:
有没有觉得上面的文本特别刺眼,难以识别?
上面的两个案例,本质上都是文本色与背景色的“对比度”不足导致的,足够的色彩“对比度”可让文字和图片更易于阅读和理解。
“对比度”是指显示屏上两种相邻颜色之间的亮度或发出光线的强度的差异计算值。这个比值的范围在 1 到 21 之间(通常写为 1:1 到 21:1);该值越大,则对比度越高。
实际上,W3C 的 Web 无障碍推进组织制定了 Web 内容无障碍指南(WCAG),这个指南涉及了一些建议,这些建议可使 Web 内容更容易访问。遵循这些原则,能够让内容更易为广大残障人士所接受,包括失明和低视力、失聪和重听、学习障碍、认知障碍、行动不便、言语残疾、光过敏患者和这些病症的复合患者。遵循这些原则也可让普通用户更容易访问 Web 内容。
简单总结一下这个标准:
对比度等级 | 普通文本 | 大型文本 |
---|---|---|
AA | 4.5:1 | 3:1 |
AAA | 7:1 | 4.5:1 |
对比度等级:
字体大小的分界线:
那么如何实现符合 WCAG 规范的对比度呢?如何在项目中渐变快捷地进行调整呢?
有很多工具可以调整对比度,这里列举一些我使用过的工具:
一个在线的,计算任意两种有效 CSS 颜色之间对比度的工具。
两侧分别为文本颜色与背景色,中间一个醒目的圆盘,显示二者的对比度,鼠标移动到这个圆盘上可以查看该对比度符合 AA/AAA 对比度标准。
对于开发的同学来说,这种方式也很快捷方便。新版的 Chrome 浏览器增强了 CSS 的调色功能:
打开控制台,选中一个 文本元素 然后在 Styles 中找到 color 属性,点击 颜色值,可以看到一个调色板。
然后点击调色板的 Contrast radio 选项,展开对比度计算工具,可以看到上方的调色板出现一条白线,这条白线就是符合 AA 级对比度标准的临界值。调色板中的圆圈就是当前选中的色值,拖动这个色值即可调整文本颜色,这时下方的对比度计算工具进行了实时计算:
通过构建工具即可在构建时进行对比度的验证,从而保证 UI 中的文本易读性符合标准。
有一些开源的 JS 库可以进行对比度的计算,这里我找到了一个很好用的库: TinyColor, 通过 readability 函数计算两个颜色的对比度:
tinycolor.readability("#000", "#000"); // 1
tinycolor.readability("#000", "#111"); // 1.1121078324840545
tinycolor.readability("#000", "#fff"); // 21
通过 isReadable 函数判断是否符合 AA/AAA 级对比度标准:
tinycolor.isReadable("#000", "#111", {}); // false
tinycolor.isReadable("#ff0088", "#5c1a72",{level:"AA",size:"small"}); //false
tinycolor.isReadable("#ff0088", "#5c1a72",{level:"AA",size:"large"}), //true
通过 JS 进行对比度的计算,举个栗子,一个最常见的场景: 手机顶部状态栏中文本非常显眼,背景色多变,但文本色的正确搭配往往被忽视,下图是我们 APP 中某个页面的状态栏:
可以看到状态栏的文本是黑色,而 header 中的文本颜色又是白色,一般来说状态栏的颜色设置为黑色或白色(iOS 中只能设置这两种颜色),这时状态栏的颜色最好根据 js 来计算,人为设置黑色或白色容易搭配不当或者遗漏配置。
在彩色背景上使用灰色文本会降低对比度:
最早是在 Material Design 的设计规范中看到的这个技巧,使用透明的黑白色文本和彩色背景的时候,文本颜色会混合成相应的深色,例如上图中的深粉色。这样做的好处是背景颜色变化的时候文本颜色会自动混合成对应的深色,不必改变文本的颜色值。
页面中色彩不宜过多,谨慎使用彩色文本,应该把彩色留给按钮、链接、开关等组件,这样做的好处是文本层次鲜明,失去控制的过多色彩会让内容缺乏重点。
正文使用黑白色(在大段的正文中使用彩色文本不利于阅读)
按照功能的重要性,为文本制定不同半透明度的规范,从而对不同层次信息的对比度加以区分。
使用不同半透明度的文本,用于区分标题、正文、描述文本、提示文本和icon。这样做可以让读者阅读起来有一定的优先级关系,可以让信息层次鲜明,有助于用户理解关键信息,减少阅读时候的疲劳感。
如果背景的色彩比较复杂(如渐变色、图案等),则可以根据平均值来作为背景色计算对比度。
如果背景色的色彩差异较大,则应在文本和背景之间添加遮罩。
即上文提到的使用 TinyColor 工具进行精确计算,在构建工具中验证。
虽然 WCAG 提出了 AA/AAA 级对比度标准,但是实际使用时应从用户角度出发,根据实际场景确定是否需要更强烈的对比度,举几个例子:
这段代码用 sass 实现了若干函数,其中:
** 1. @function mdc-theme-contrast-tone ** 传入一个颜色值,返回该颜色背景上应该使用的颜色为 light / dark 颜色
** 2. @function mdc-theme-tone ** 传入一个颜色值,返回该颜色为 light / dark 颜色,核心代码:
// 传入的颜色值为 $color
$lightContrast: mdc-theme-contrast($color, white); // 计算传入颜色和白色的对比度
$darkContrast: mdc-theme-contrast($color, rgba(black, .87)); // 计算传入颜色和黑色的对比度
$minimumContrast: 3.1; // 设置最小对比度
@if ($lightContrast < $minimumContrast) and ($darkContrast > $lightContrast) {
// 传入的颜色值和白色的对比度小于最小对比度 并且 和黑色的对比度更高
@return "light"; // 我们认为传入的颜色是浅色
} @else {
// 我们认为传入的颜色是深色
@return "dark";
}
3.👆 上面的代码中用到了计算对比度的函数 mdc-theme-contrast 也在这段代码中,对比度是这样计算的:
// 计算颜色的相对亮度(relative luminance)
@function mdc-theme-luminance($color) {
$red: nth($mdc-theme-linear-channel-values, red($color) + 1);
$green: nth($mdc-theme-linear-channel-values, green($color) + 1);
$blue: nth($mdc-theme-linear-channel-values, blue($color) + 1);
@return .2126 * $red + .7152 * $green + .0722 * $blue;
}
// 计算两个颜色的对比度(contrast ratio)
@function mdc-theme-contrast($back, $front) {
$backLum: mdc-theme-luminance($back) + .05;
$foreLum: mdc-theme-luminance($front) + .05;
@return max($backLum, $foreLum) / min($backLum, $foreLum);
}
这里用到了一个对比度计算公式: https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests
可以看到一些有趣的参数:
@return .2126 * $red + .7152 * $green + .0722 * $blue;
相同的色值,绿色要显得亮一些,相反蓝色要显得暗一些。
Material Design 在计算传入颜色和黑色的对比度时,传入的是一个带有半透明度的色值,实际上在执行时会按照纯黑色(不带有半透明度)进行解析。
$darkContrast: mdc-theme-contrast($color, rgba(black, .87)); // 计算传入颜色和黑色的对比度
我理解的 rgba(black, .87) 指的是 Material Design 规定的正文字体颜色(深色),因此这里按照半透明色进行解析会更好一些,这样我们判断颜色是深色还是浅色时的依据就是页面上最深的文本颜色 (#000 87%) 和最浅的背景颜色 (#fff 100%) 了。
用 js 来实现这个混合的过程 (依赖 tinycolor):
// Alpha 合成 支持前景色半透明
function mixColor(front, back) {
var rgbFront = tinycolor(front).toRgb();
var rgbBack = tinycolor(back).toRgb();
var alphaFront = rgbFront.a;
var mixR = alphaFront * rgbFront.r + (1 - alphaFront) * rgbBack.r;
var mixG = alphaFront * rgbFront.g + (1 - alphaFront) * rgbBack.g;
var mixB = alphaFront * rgbFront.b + (1 - alphaFront) * rgbBack.b;
var res = tinycolor({ r: mixR, g: mixG, b: mixB }).toHexString();
return res;
}
Alpha 合成算法来自: 维基百科 - Alpha 合成
Material Design 中最小对比度是写死的 3.1,不知道为什么没有设置为 AA/AAA 级对比度标准,这里可以传入参数来指定最小对比度:
function contrast(color, contrast) {
if (color === 'light' || color === 'dark') return color;
var minimumContrast = contrast || 3.5;
var lightContrast = getContrast(color, '#fff');
var darkContrast = getContrast(color, '#000');
if (lightContrast < minimumContrast && darkContrast > lightContrast) {
return 'light';
} else {
return 'dark';
}
}
上文提到的 Material Design 的源码包含的几个函数,在 TinyColor 这个工具里都有实现。
我按照 Material Design 的实现思路,用 js 实现了一遍,增加了上文提到的两个优化点 (支持半透明度颜色混合 / 支持调节最小对比度): 源码地址