如何根据当前颜色生成相反的颜色?

134

我正在尝试创建一个与当前颜色相反的颜色。我的意思是,如果当前颜色是黑色,那么我需要生成白色

实际上,我有一个文本(此文本的颜色是动态的,可以随机生成)。该文本位于一个div中,我需要为divbackground-color设置该文本的相反颜色。我希望文本在div(颜色方面)清晰可见

相反颜色指:暗/亮

我有文本的当前颜色,并可以将其传递给此函数:

var TextColor = #F0F0F0;    // for example (it is a bright color)

function create_opp_color(current color) {

    // create opposite color according to current color

}

create_opp_color(TextColor); // this should be something like "#202020" (or a dark color)

有没有想法创建create_opp_color()函数?


可能是 https://dev59.com/PXI-5IYBdhLWcg3wy7-n 的重复问题。 - mplungjan
黑暗/明亮?那么红色(#FF0000)的对立面是...黑色(#000000)?对于黑白"轴"来说很容易,但如果期望的目标是获得"对比"而不是"互补"颜色或类似的东西,处理颜色可能会很棘手。 - miguel-svq
@miguel-svq 很好的观点..我的目标是使文本易读(从颜色角度),所以如果文本的颜色是红色,那么背景的颜色几乎可以是任何颜色,如黑色、白色、蓝色.. - stack
有一些非常好的用于操作颜色的模块。例如,看看 tinycolor(https://github.com/bgrins/TinyColor),它有一个 mostReadable 函数。我认为这比自己编写一个更好。 - Elmar Zander
为什么我们应该在数字前面添加零填充,以及为什么我们应该删除它们?有人能解释一下吗? - indianwebdevil
14个回答

344

更新: 在 GitHub 上发布了可用于生产的代码。


我的做法如下:

  1. 将 HEX 转换为 RGB
  2. 反转 R、G 和 B 组件
  3. 将每个组件转回 HEX
  4. 在每个组件前填充零并输出。
function invertColor(hex) {
    if (hex.indexOf('#') === 0) {
        hex = hex.slice(1);
    }
    // convert 3-digit hex to 6-digits.
    if (hex.length === 3) {
        hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
    }
    if (hex.length !== 6) {
        throw new Error('Invalid HEX color.');
    }
    // invert color components
    var r = (255 - parseInt(hex.slice(0, 2), 16)).toString(16),
        g = (255 - parseInt(hex.slice(2, 4), 16)).toString(16),
        b = (255 - parseInt(hex.slice(4, 6), 16)).toString(16);
    // pad each with zeros and return
    return '#' + padZero(r) + padZero(g) + padZero(b);
}

function padZero(str, len) {
    len = len || 2;
    var zeros = new Array(len).join('0');
    return (zeros + str).slice(-len);
}

示例输出:

在此输入图片描述

高级版本:

此版本具有选项,该选项将决定是反转为黑色还是白色。这样您会获得更高的对比度,通常对人眼更好。

function invertColor(hex, bw) {
    if (hex.indexOf('#') === 0) {
        hex = hex.slice(1);
    }
    // convert 3-digit hex to 6-digits.
    if (hex.length === 3) {
        hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
    }
    if (hex.length !== 6) {
        throw new Error('Invalid HEX color.');
    }
    var r = parseInt(hex.slice(0, 2), 16),
        g = parseInt(hex.slice(2, 4), 16),
        b = parseInt(hex.slice(4, 6), 16);
    if (bw) {
        // https://dev59.com/sW865IYBdhLWcg3wLrhE#3943023
        return (r * 0.299 + g * 0.587 + b * 0.114) > 186
            ? '#000000'
            : '#FFFFFF';
    }
    // invert color components
    r = (255 - r).toString(16);
    g = (255 - g).toString(16);
    b = (255 - b).toString(16);
    // pad each with zeros and return
    return "#" + padZero(r) + padZero(g) + padZero(b);
}

示例输出:

在此输入图片描述


1
这是对“相反”的问题的答案,将颜色设置为具有背景颜色的前景色,几乎相同。根据原始问题相应地更改小样可以导致低对比度颜色(没有“bw”)或黑白背景 https://jsfiddle.net/cffcd5bj/5/。希望@stack适应它以避免不需要的低对比度颜色对。 - miguel-svq
1
是的,这就是所谓的微型库。更易于维护,支持SRP等。 - Onur Yıldırım
1
使用这样简单的计算(根据255获取对称)来获取颜色的反色,如果你的颜色像#808080,那么就无法起作用。在这种情况下,颜色的反色等于它本身。 - canbax
2
我使用内置的.padStart函数进行返回。返回值为 "#" + r.padStart(2, '0') + g.padStart(2, '0') + b.padStart(2, '0'); https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/padStart - Joe Johnston
1
@OnurYıldırım添加了积分。不过这是个好提醒。 - indianwebdevil
显示剩余12条评论

59

简单而优雅。

function invertHex(hex) {
  return (Number(`0x1${hex}`) ^ 0xFFFFFF).toString(16).substr(1).toUpperCase()
}

invertHex('00FF00'); // Returns FF00FF

6
不知道它是干什么的或者是如何工作的,但它确实简单而优雅 :) - Jeremy Thille
2
如果有人能够解释它的工作原理,我会非常高兴。 - Ar2zee
9
Number(0x1${hex})首先将给定的十六进制(字符串)值转换为十六进制数。 ^ 0xFFFFFF然后对该值使用“0xFFFFFF”进行按位运算,我现在只能像这样解释它,如果您想尽快反转一个数字,例如3>7、8>2、1>9等,只需用该数字减去10并取绝对值。这就是该部分除了十六进制值之外的作用。其余部分基本上是格式化并删除公式中的第一个值,该值是公式的溢出部分。 - Gerardlamo
2
非常棒而简单,但需要注意它“在颜色的中间进行镜像”,因此靠近中间的颜色会变得非常接近。例如,invertHex('808080'); 会产生'7F7F7F',它几乎是相同的颜色。 - MoonLite
厉害!解释: 按位运算符 ^:如果两个二进制位只有一个为1,则设置每个位为1; toString(16):数字将显示为十六进制值; substr(1):返回结果字符串,但跳过第一个项目; toUpperCase():这非常明显。 - BaseScript

29

CSS实现这一点的简单方法:

mix-blend-mode: difference;
color:white;

2
注意:mix-blend-mode目前不受!E或Microsoft Edge等浏览器支持,根据您的用例,可能不太可能优雅地失败。 - Dan Devine
1
当背景为灰色时,文本是不可见的。你测试过了吗? - CS QGB
那要怎么做呢?这个和其他地方提到的反转解决方案都需要多个元素,因为反转/混合是应用于整个元素的。具体说到楼主的问题,这个答案甚至没有改变背景(这正是楼主想要的)。 - undefined

16

这是对 @Onur 的 答案 中的 bw 部分的纯 CSS 实现。

  <input type="color" oninput="['--r','--g','--b'].forEach((k,i)=>this.nextElementSibling.style.setProperty(k,parseInt(event.target.value.slice(1+i*2,3+i*2),16)))" />
 
  <div style="--r: 0; --g: 0; --b: 0; --c: calc(-1 * ((var(--r) * 0.299 + var(--g) * 0.587 + var(--b) * 0.114) - 186) * 255)">
    <div style="background-color: rgb(var(--r), var(--g), var(--b)); color: rgb(var(--c), var(--c), var(--c))">Test</div>
  </div>


12

如何使用 CSS 的 filter: invert(1),它具有良好的 跨浏览器兼容性 ,并且可以用于文本、图像或任何内容。

要生成黑白反转颜色,请添加更多滤镜 filter: saturate(0) grayscale(1) brightness(.7) contrast(1000%) invert(1)

这里是一个 ColorPicker 示例(注意文本颜色):

const colorPicker = document.querySelector("input");
const background = document.querySelector("div");
const invertedText = document.querySelector("b");

colorPicker.oninput = (e) => {
  const color = e.target.value;
  background.style.background = color;
  invertedText.style.color = color;
  invertedText.innerText = color;
}
body {
  font-family: Arial;
  background: #333;
}

div {
  position: relative;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  min-width: 100px;
  padding: .5em 1em;
  border: 2px solid #FFF;
  border-radius: 15px;
  background: #378ad3;
}

b {
  /* Inverting the color ᐯᐯᐯᐯᐯᐯᐯᐯᐯᐯᐯᐯ */
  filter: saturate(0) grayscale(1) brightness(.7) contrast(1000%) invert(1);
}

input {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}
<div>
  <b>#378ad3</b>
  <input type="color" value="#378ad3"/>
</div>


1
这个解决方案在前端方面是最优的。 - Olivier C
1
我已经寻找很长时间了。你的saturate(0) grayscale(1) brightness(.7) contrast(1000%) invert(1)解决方案真是太棒了。 - Shashank Agrawal
1
这太好了,而且很简单明了,CSS让它易于使用,无需任何js。 - Emmanuel David
对于 #b8b8b8 这个颜色,文字是无法阅读的。 - undefined

7
请注意辅助功能(AA / AAA)。仅仅颜色对比本身是无用的。对于色盲人来说,非常不同的颜色可能根本没有对比度。我认为可以按照以下方式计算这种颜色:
(为简单起见使用“HLS”)
- 旋转色相180º以获得(可能无用的)最大颜色对比度 - 计算亮度差异。 - (计算颜色差异...不必要,它是最大的或几乎是最大的) - 计算对比度比率。 - 如果所得颜色符合要求,则计算结束;如果不符合要求,请重复以下步骤:
- 如果亮度差异不足,请增加或减少计算出的颜色亮度(L)一定数量或比率(向上或向下取决于原始颜色的亮度:>或<中值) - 检查是否符合要求,如果符合则计算结束。 - 如果亮度不能再增加(或降低),那么就没有有效的颜色符合要求,请尝试黑白两色,并选择其中“最好”的一个(可能是具有更大对比度比率的那个),然后结束。

4
在我理解您的问题时,您所说的相反颜色是指反转的颜色。
InvertedColorComponent = 0xFF - ColorComponent

对于颜色红色(#FF0000)的意思是: R = 0xFF或者255 G = 0x00或者0 B = 0x00或者0

反转颜色红色(#00FFFF)是:

R = 0xFF - 0xFF = 0x00 or 255 - 255 = 0
G = 0xFF - 0x00 = 0xFF or 255 - 0 = 255
B = 0xFF - 0x00 = 0xFF or 255 - 0 = 255

其他例子:

黑色(#000000)变为白色(#FFFFFF)。

橙色(#FFA500)变为#005AFF。


我也会使用“倒置”,但请记住,“相反”还可以指“在色轮上相反”。 - Engineer

3

这是一个简单的函数,用于反转十六进制颜色。

const invertColor = (col) => {
col = col.toLowerCase();
  const colors = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f']
  let inverseColor = '#'
  col.replace('#','').split('').forEach(i => {
    const index = colors.indexOf(i)
    inverseColor += colors.reverse()[index]
  })
  return inverseColor
}

Codepen 示例


2
仅仅将背景色翻转为文本颜色并不能处理某些中间值,例如0x808080。我尝试过改变颜色值 - (v + 0x80) % 0x100。在这里可以看到演示here

同意miguel-svq的评论 - 尽管期望看到每个计算步骤的更详细算法。

2

可以使用片段将HEX颜色转换。

function invertColor(color) {
      return '#' + ("000000" + (0xFFFFFF ^ parseInt(color.substring(1),16)).toString(16)).slice(-6);
  }

1
这很好,这是一种快速获取实用反转的方法。 - Basav

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