如何根据背景颜色来确定字体颜色是白色还是黑色?

374

我想像这个例子一样展示一些图片alt text

填充颜色由数据库中的字段决定,用16进制表示颜色(例如:ClassX -> 颜色:#66FFFF)。 现在,我想在所选颜色的上方显示数据(如上图所示),但是我需要知道颜色是深还是浅,以便知道文字应该是白色或黑色。 有方法吗?谢谢


相关(但也有重复):https://dev59.com/sW865IYBdhLWcg3wLrhE https://dev59.com/DXA75IYBdhLWcg3wxcHV - JYelton
27个回答

573

在我回答类似问题的帖子的基础上进行讨论。

你需要将十六进制代码分为三部分,以获取红、绿和蓝色的亮度。该代码的每2个数字代表十六进制(基数16)中的一个值。我不会在此讨论转换的详细信息,这些详细信息很容易查找。

一旦你获得了各个颜色的强度,你就可以确定颜色的整体强度并选择相应的文本。

if (red*0.299 + green*0.587 + blue*0.114) > 186 use #000000 else use #ffffff

阈值186是基于理论的,但可以根据个人喜好进行调整。根据下面的评论,对于你来说,使用150的阈值可能效果更好。


编辑:以上方法简单且效果合理,在StackOverflow社区中得到了很好的接受度。然而,下面的一条评论显示它在某些情况下会导致不符合W3C准则的情况。这里我提出了修改后的公式,该公式始终基于准则选择最高对比度。如果您不需要符合W3C规则,那么我建议您坚持使用上述更简单的公式。有关此问题的有趣讨论,请参见对比度比例数学和相关视觉问题

W3C建议(WCAG 2.0)中给出了对比度的公式为:(L1 + 0.05) / (L2 + 0.05),其中L1是最亮颜色的亮度,L2是最暗颜色的亮度,取值范围为0.0-1.0。黑色的亮度为0.0,白色的亮度为1.0,因此将这些值代入该公式可以确定具有最高对比度的颜色。如果黑色的对比度大于白色,则使用黑色,否则使用白色。给定您正在测试的颜色的亮度为L,测试如下:

if (L + 0.05) / (0.0 + 0.05) > (1.0 + 0.05) / (L + 0.05) use #000000 else use #ffffff

这可以简化为代数式:

if L > sqrt(1.05 * 0.05) - 0.05

或者近似地说:

if L > 0.179 use #000000 else use #ffffff
唯一剩下的任务就是计算L。该公式也在指南中给出,看起来需要将sRGB转换为线性RGB,然后按照ITU-R建议BT.709的亮度进行转换。
for each c in r,g,b:
    c = c / 255.0
    if c <= 0.04045 then c = c/12.92 else c = ((c+0.055)/1.055) ^ 2.4
L = 0.2126 * r + 0.7152 * g + 0.0722 * b

0.179的阈值不应该改变,因为它与W3C指南有关。如果你发现结果不符合你的要求,尝试使用上述更简单的公式。


3
谢谢,Mark。尝试了一些更改:仅用第一个数字计算红、绿、蓝(不太精确但是这个数字具有更高的权重),并且使用9代替186。对我来说效果略好,特别是对于绿色。 - DJPB
4
这个公式是错误的,错误很大。举个例子,它给黄色 #D6B508 一个值为171,因此得到白色对比度。然而,对比度应该是黑色(在这里确认:http://webaim.org/resources/contrastchecker/)。 - McGarnagle
3
这是另一个示例,比较了此答案中给出的两个公式。您可以使用最近版本的Firefox或Chrome中的颜色选择器来检查与任何颜色的对比度。 - chetstone
7
在玩了@chetstone的演示一会儿后,我认为简单公式对我来说效果最好,但是我不同意阈值建议。 根据纯绿色的结果,我尝试将阈值设置为149,在我看来这样效果更好。 我制作了一个非常愚蠢的小测试来展示,您可以尝试将顶部的阈值更改回原始建议。 - Miral
4
对我来说,将数字186改为150可以在背景颜色和文字颜色之间提供良好的对比度。 - Oshada
显示剩余25条评论

105

我并不是这段代码的作者,所以我不会为此负责。但是我将它留在这里,方便其他人在未来快速查找:

根据Mark Ransoms的回答,以下是一个简单版本的代码片段:

function pickTextColorBasedOnBgColorSimple(bgColor, lightColor, darkColor) {
  var color = (bgColor.charAt(0) === '#') ? bgColor.substring(1, 7) : bgColor;
  var r = parseInt(color.substring(0, 2), 16); // hexToR
  var g = parseInt(color.substring(2, 4), 16); // hexToG
  var b = parseInt(color.substring(4, 6), 16); // hexToB
  return (((r * 0.299) + (g * 0.587) + (b * 0.114)) > 186) ?
    darkColor : lightColor;
}

以下是高级版的代码片段:

function pickTextColorBasedOnBgColorAdvanced(bgColor, lightColor, darkColor) {
  var color = (bgColor.charAt(0) === '#') ? bgColor.substring(1, 7) : bgColor;
  var r = parseInt(color.substring(0, 2), 16); // hexToR
  var g = parseInt(color.substring(2, 4), 16); // hexToG
  var b = parseInt(color.substring(4, 6), 16); // hexToB
  var uicolors = [r / 255, g / 255, b / 255];
  var c = uicolors.map((col) => {
    if (col <= 0.03928) {
      return col / 12.92;
    }
    return Math.pow((col + 0.055) / 1.055, 2.4);
  });
  var L = (0.2126 * c[0]) + (0.7152 * c[1]) + (0.0722 * c[2]);
  return (L > 0.179) ? darkColor : lightColor;
}

要使用它们,只需调用:

var color = '#EEACAE' // this can be any color
pickTextColorBasedOnBgColorSimple(color, '#FFFFFF', '#000000');

此外,感谢Alxchetstone


2
我使用了简单的函数,并将其简化了一些:删除了最后两个参数,并将其重命名为isDark(bgColor)。然后我的用法就是'color': isDark(color)?'white':'black' - diynevala
1
对我来说非常好用。非常感谢!我用的是 PHP,只需转换语法和函数即可。 - CezarBastos
4
请注意,马克计算 L>0.179 假设我们仅在白色和黑色之间选择作为文本颜色。选择其他颜色需要重新计算此阈值,因此将任意颜色传递为 lightColordarkColor 并不是很合理。 - Aaron Dunigan AtLee
1
我在高级示例中一直收到 NaN - coler-j

28

除了算法解决方案之外,还可以使用AI神经网络。优点在于可以根据自己的口味和需求进行定制(例如,在鲜艳的红色背景上使用淡白色文本看起来不错,而且与黑色一样易读)。

这里有一个漂亮的Javascript演示,展示了这个概念。您还可以在演示中生成自己的JS公式。

https://harthur.github.io/brain/

以下是一些图表,帮助我理解这个问题。 在第一张图表中,亮度是恒定的128,而色调和饱和度变化。在第二张图表中,饱和度是恒定的255,而色调和亮度变化。

在第一张图表中,亮度是恒定的128,而色调和饱和度变化:

饱和度是恒定的255,而色调和亮度变化:


3
这是一种优秀的方式来直观地显示选项。 - Zac
1
我已经将我的文章重新发布在Medium上,因为我的旧博客不再存在了。 https://medium.com/@think_ui/visualizing-color-contrast-a-guide-to-using-black-and-white-text-on-colored-backgrounds-14346a2e5680 - Roger Attrill
嗨,@RogerAttrill,我在帖子中更新了您中等文章的链接... - Myndex
提供在 https://harthur.github.io/brain/ 下的代码最初对我来说无法工作,因为 bgColor 输入未被记录。 这是格式: var string = ' #f876a9'; var htmlHexa = string.trim(); var rgbHexa = htmlHexa.charAt(0) === '#' ? htmlHexa.substring(1, 7) : htmlHexa; var bgColor = { r: parseInt(rgbHexa.substring(0, 2), 16) / 255, g: parseInt(rgbHexa.substring(2, 4), 16) / 255, b: parseInt(rgbHexa.substring(4, 6), 16) / 255, }; var textColor = function (bgColor) { ... - Bruno J. S. Lesieur

25

这段 JavaScript 代码怎么样?

/**
 * Get color (black/white) depending on bgColor so it would be clearly seen.
 * @param bgColor
 * @returns {string}
 */
getColorByBgColor(bgColor) {
    if (!bgColor) { return ''; }
    return (parseInt(bgColor.replace('#', ''), 16) > 0xffffff / 2) ? '#000' : '#fff';
}

2
这通常能够正常工作,但也有像 http://i.imgur.com/3pOUDe5.jpg 这样看起来奇怪的情况,背景颜色实际上是 rgb(6, 247, 241)。 - madprops
我很欣赏这种相对便宜的方法,可以在你还没有将颜色转换为RGB值的情况下执行对比度计算(虽然这并不太困难,只是需要额外的数学步骤)。 - frumbert

17
如果您和我一样在寻找一个考虑alpha通道的RGBA版本,这里有一个非常有效的版本可以实现高对比度。
function getContrastColor(R, G, B, A) {
  const brightness = R * 0.299 + G * 0.587 + B * 0.114 + (1 - A) * 255;

  return brightness > 186 ? "#000000" : "#FFFFFF";
}

1
只是为了给你点赞才登录的,没找到有人注意到这一点。 - robertjuh
@robertjuh 我也很少在stackoverflow上发帖,所以感谢你的努力 :) - Germain

11

这是我一直在使用并且迄今为止没有任何问题的方法:

function hexToRgb(hex) {
    const hexCode = hex.charAt(0) === '#' 
                        ? hex.substr(1, 6)
                        : hex;

    const hexR = parseInt(hexCode.substr(0, 2), 16);
    const hexG = parseInt(hexCode.substr(2, 2), 16);
    const hexB = parseInt(hexCode.substr(4, 2), 16);
    // Gets the average value of the colors
    const contrastRatio = (hexR + hexG + hexB) / (255 * 3);

    return contrastRatio >= 0.5
        ? 'black'
        : 'white';
}

8

以下是我在Android中使用Java的解决方案:

// Put this method in whichever class you deem appropriate
// static or non-static, up to you.
public static int getContrastColor(int colorIntValue) {
    int red = Color.red(colorIntValue);
    int green = Color.green(colorIntValue);
    int blue = Color.blue(colorIntValue);
    double lum = (((0.299 * red) + ((0.587 * green) + (0.114 * blue))));
    return lum > 186 ? 0xFF000000 : 0xFFFFFFFF;
}

// Usage
// If Color is represented as HEX code:
String colorHex = "#484588";
int color = Color.parseColor(colorHex);

// Or if color is Integer:
int color = 0xFF484588;

// Get White (0xFFFFFFFF) or Black (0xFF000000)
int contrastColor = WhateverClass.getContrastColor(color);

2
真的“完美”吗?尝试纯绿色背景,#00FF00。 - Andreas Rejbrand
这是真的,这并没有针对所有颜色进行测试...但是谁会使用纯绿色的背景来设计任何不想让用户感到恼怒的东西呢? - mwieczorek
5
依靠用户生成的内容或随机选择颜色的人会这样做。 - Marc Plano-Lesay

6
这是Mark Ransom的答案的Swift版本,作为UIColor的扩展。
extension UIColor {

// Get the rgba components in CGFloat
var rgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
    var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0

    getRed(&red, green: &green, blue: &blue, alpha: &alpha)

    return (red, green, blue, alpha)
}

/// Return the better contrasting color, white or black
func contrastColor() -> UIColor {
    let rgbArray = [rgba.red, rgba.green, rgba.blue]

    let luminanceArray = rgbArray.map({ value -> (CGFloat) in
        if value < 0.03928 {
            return (value / 12.92)
        } else {
            return (pow( (value + 0.55) / 1.055, 2.4) )
        }
    })

    let luminance = 0.2126 * luminanceArray[0] +
        0.7152 * luminanceArray[1] +
        0.0722 * luminanceArray[2]

    return luminance > 0.179 ? UIColor.black : UIColor.white
} }

6

根据@MarkRansom的回答,我创建了一个PHP脚本,您可以在此处找到:

function calcC($c) {
    if ($c <= 0.03928) {
        return $c / 12.92;
    }
    else {
        return pow(($c + 0.055) / 1.055, 2.4);
    }
}

function cutHex($h) {
    return ($h[0] == "#") ? substr($h, 1, 7) : $h;
}

function hexToR($h) {
    return hexdec(substr(cutHex($h), 0, 2));
}

function hexToG($h) {
    return hexdec(substr(cutHex($h), 2, 2)); // Edited
}

function hexToB($h) {
    return hexdec(substr(cutHex($h), 4, 2)); // Edited
}

function computeTextColor($color) {
    $r = hexToR($color);
    $g = hexToG($color);
    $b = hexToB($color);
    $uicolors = [$r / 255, $g / 255, $b / 255];


    $c = array_map("calcC", $uicolors);

    $l = 0.2126 * $c[0] + 0.7152 * $c[1] + 0.0722 * $c[2];
    return ($l > 0.179) ? '#000000' : '#ffffff';
}

3

我基于@SudoPlz提出的高级函数编写了一个函数,该函数还考虑了浅色和深色颜色:

function getTextColor (bgColor, lightColor = '#FFFFFF', darkColor = '#000000') {

  const getLuminance = function (hexColor) {
    var color = (hexColor.charAt(0) === '#') ? hexColor.substring(1, 7) : hexColor
    var r = parseInt(color.substring(0, 2), 16) // hexToR
    var g = parseInt(color.substring(2, 4), 16) // hexToG
    var b = parseInt(color.substring(4, 6), 16) // hexToB
    var uicolors = [r / 255, g / 255, b / 255]
    var c = uicolors.map(col => col <= 0.03928 ? col / 12.92 : ((col + 0.055) / 1.055) ** 2.4)

    return (0.2126 * c[0]) + (0.7152 * c[1]) + (0.0722 * c[2]);
  }

  var L = getLuminance(bgColor)
  var L1 = getLuminance(lightColor)
  var L2 = getLuminance(darkColor)

  return (L > Math.sqrt((L1 + 0.05) * (L2 + 0.05)) - 0.05) ? darkColor : lightColor;
}

因此,如果暗色文本不是黑色而是褐紫色,则在灰色背景上建议使用白色作为文本颜色。
getTextColor('#808080')
"#000000"

getTextColor('#808080', '#FFFFFF', '#800000')
"#FFFFFF"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接