根据覆盖背景区域的亮度改变文本颜色?

170
我正在寻找一种插件或技术,可以根据其父元素的background-image或background-color所覆盖的像素的平均亮度来改变文本的颜色或在预定义的图像/图标之间切换。
如果它的背景的覆盖面积比较暗,则将文本设置为白色或切换图标。
此外,如果脚本注意到父元素没有定义 background-color 或 background-image ,则继续搜索最近的(从父元素到其父元素)。
您对这个想法有何看法?是否已经出现类似的东西?示例?

1
这只是一个想法而不是答案。可能有一种使用HSL设置颜色的方法,然后查看亮度值。如果该值高于某个值,则应用CSS规则。 - user527892
1
你可以将元素的背景颜色解析为R、G、B(和可选的alpha)值,如果alpha通道设置为零,则向上遍历DOM树。然而,尝试确定背景图像的颜色则完全是另一回事。 - jackwanders
已经在这里回答了:https://dev59.com/v2035IYBdhLWcg3wC7mf - Pascal
@Pascal 很相似,输入也不错... 但这并不是我问题的确切答案。 - James Cazzetta
9个回答

243

以下是相关资源:

这是 W3C 的算法(同时提供 JSFiddle 演示

const rgb = [255, 0, 0];

// Randomly change to showcase updates
setInterval(setContrast, 1000);

function setContrast() {
  // Randomly update colours
  rgb[0] = Math.round(Math.random() * 255);
  rgb[1] = Math.round(Math.random() * 255);
  rgb[2] = Math.round(Math.random() * 255);

  // http://www.w3.org/TR/AERT#color-contrast
  const brightness = Math.round(((parseInt(rgb[0]) * 299) +
                      (parseInt(rgb[1]) * 587) +
                      (parseInt(rgb[2]) * 114)) / 1000);
  const textColour = (brightness > 125) ? 'black' : 'white';
  const backgroundColour = 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')';
  $('#bg').css('color', textColour); 
  $('#bg').css('background-color', backgroundColour);
}
#bg {
  width: 200px;
  height: 50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="bg">Text Example</div>


1
可以缩短为以下代码,只要传递一个对象 ::::const setContrast = rgb => (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000 > 125 ? '黑色' : '白色' - Luke Robertson
3
你是否真的需要使用jQuery来改变CSS? - B''H Bi'ezras -- Boruch Hashem
@bluejayke 不,还有其他方法;-) - Alex Ball

121

这篇关于计算颜色对比度的24 Ways文章可能会引起你的兴趣。忽略第一组函数,因为它们是错误的,但是YIQ公式可以帮助你确定是否使用浅色或深色前景色。

一旦获取了元素(或祖先)的背景颜色,就可以使用这篇文章中的函数来确定一个适合的前景色:

function getContrastYIQ(hexcolor){
    var r = parseInt(hexcolor.substring(1,3),16);
    var g = parseInt(hexcolor.substring(3,5),16);
    var b = parseInt(hexcolor.substring(5,7),16);
    var yiq = ((r*299)+(g*587)+(b*114))/1000;
    return (yiq >= 128) ? 'black' : 'white';
}

谢谢,这真的很有帮助。这取决于设置的背景颜色。但是你知道如何通过遍历每个像素(例如在循环中)来获取图像的平均颜色吗? - James Cazzetta
6
在 ES6 中,您可以使用以下代码实现此功能:const getContrastYIQ = hc => { const [r, g, b] = [0, 2, 4].map( p => parseInt( hc.substr( p, 2 ), 16 ) ); return ((r * 299) + (g * 587) + (b * 114)) / 1000 >= 128; }该函数接受一个颜色值,并计算出其在 YIQ 颜色空间中的亮度。如果该颜色值的亮度大于或等于 128,则返回 true,否则返回 false。 - Centril
我稍微扩展了这个函数,以便您可以返回两种自定义颜色,而不是始终为黑色和白色。请注意,如果颜色太接近,仍可能存在对比问题,但这是返回绝对颜色的良好替代方案。https://jsfiddle.net/1905occv/1/ - Hanna
3
这个不错,我只会把yiq调整到>=160,这对我效果更好。 - Arturo

24

mix-blend-mode可以达到效果:

header {
  overflow: hidden;
  height: 100vh;
  background: url(https://www.w3schools.com/html/pic_mountain.jpg) 50%/cover;
}

h2 {
  color: white;
  font: 900 35vmin/50vh arial;
  text-align: center;
  mix-blend-mode: difference;
  filter: drop-shadow(0.05em 0.05em orange);
}
<header>
  <h2 contentEditable role='textbox' aria-multiline='true' >Edit me here</h2>
</header>

补充(2018年3月):以下是一份很好的教程,解释了所有不同类型的模式/实现:https://css-tricks.com/css-techniques-and-effects-for-knockout-text/


1
很惊讶这里没有更高的评价- 这对我来说完美运作,并且非常容易实现。 - ShaneOH
1
这不是OP所要求的。这会反转颜色,如果你有绿色背景,你会得到橙色文本。 - Gugalcrom123

16

有趣的问题。我的第一反应是将背景颜色与文本颜色反转。这只涉及解析背景并反转其RGB值。

可以像这样实现:http://jsfiddle.net/2VTnZ/2/

var rgb = $('#test').css('backgroundColor');
var colors = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
var brightness = 1;

var r = colors[1];
var g = colors[2];
var b = colors[3];

var ir = Math.floor((255-r)*brightness);
var ig = Math.floor((255-g)*brightness);
var ib = Math.floor((255-b)*brightness);

$('#test').css('color', 'rgb('+ir+','+ig+','+ib+')');

1
这个怎么样?https://dev59.com/UXE85IYBdhLWcg3w8IM4 - jeremyharris
3
背景颜色如果是 #808080 会怎样呢? - Nathan MacInnes
1
@NathanMacInnes 它仍然会反转它,只是碰巧在频谱的中间反转某些内容会导致本身。这段代码只是反转了颜色,具有其局限性。 - jeremyharris
如果文本没有背景颜色,但其父元素有呢? - reggie
@reggie,你的工作是选择正确的选择器来选择颜色,这与问题无关。 - Petroff
显示剩余3条评论

7

在ES6中,可以使用以下一行代码计算与HEX 6字符颜色字符串(#123456)对比的颜色:

const contrastColor = c=>["#000","#fff"][~~([.299,.587,.114].reduce((r,v,i)=>parseInt(c.substr(i*2+1,2),16)*v+r,0)<128)];

这里是易理解的版本:

const contrastColor = color =>
{
  const lum = [.299 /*red*/,.587 /*green*/,.114 /*blue*/].reduce((result, value, index) => 
  {
    // with reduce() we can convert an array of numbers into a single number
    // result = previous result returned by this function
    // value = https://www.w3.org/TR/AERT/#color-contrast
    // index = current position index in the array
    // num = decimal number of Red, Green or Blue color
    const num = parseInt(color.substr(index * 2 + 1, 2), 16);
    return num * value + result;
  }, 0 /* result = 0 */);

  const isDark = lum < 128;
  const index = ~~isDark; // convert boolean into 0 or 1
  return ["#000","#fff"][index];
}

function setColors()
{

  for(let i = 0; i < 70; i++)
  {
    const bg = "#" + (~~(Math.random() * 16777216)).toString(16).padStart(6, 0),
          color = contrastColor(bg);
    node = test.children[i] || document.createElement("span");
    node.style.backgroundColor = bg;
    node.style.color = color;
    node.textContent = bg;
    if (!node.parentNode)
      test.appendChild(node);
  }
}

setColors();
#test
{
  display: flex;
  flex-wrap: wrap;
  font-family: monospace;
}
#test > *
{
  padding: 0.3em;
}
<button id="click" onclick="setColors()">change</button>
<div id="test"></div>


7

结合 @alex-ball 和 @jeremyharris 的答案,我发现以下方法最适合我:

        $('.elzahaby-bg').each(function () {
            var rgb = $(this).css('backgroundColor');
            var colors = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);

            var r = colors[1];
            var g = colors[2];
            var b = colors[3];

            var o = Math.round(((parseInt(r) * 299) + (parseInt(g) * 587) + (parseInt(b) * 114)) /1000);

            if(o > 125) {
                $(this).css('color', 'black');
            }else{
                $(this).css('color', 'white');
            }
        });
*{
    padding: 9px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js"></script>
<div class='elzahaby-bg' style='background-color:#000'>color is white</div>

<div class='elzahaby-bg' style='background-color:#fff'>color is black</div>
<div class='elzahaby-bg' style='background-color:yellow'>color is black</div>
<div class='elzahaby-bg' style='background-color:red'>color is white</div>


6

如果您正在使用ES6,将hex转换为RGB,然后可以使用以下代码:

const hexToRgb = hex => {
    // turn hex val to RGB
    const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
    return result
        ? {
              r: parseInt(result[1], 16),
              g: parseInt(result[2], 16),
              b: parseInt(result[3], 16)
          }
        : null
}

// calc to work out if it will match on black or white better
const setContrast = rgb =>
    (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000 > 125 ? 'black' : 'white'

const getCorrectColor = setContrast(hexToRgb(#ffffff))

6
我发现 BackgroundCheck 脚本非常有用。
它检测背景的整体亮度(无论是背景图像还是颜色),并根据背景的亮度将一个类应用于指定的文本元素 (background--lightbackground--dark)。
它可以应用于静态和动态元素。
(来源:SitePoint)

这对背景颜色有效吗?我快速阅读了脚本,没有看到它利用背景颜色来检查亮度。只有图片。 - Jørgen Skår Fischer
1
你好Jørgen,我认为colourBrightness脚本可能符合你的需求:https://github.com/jamiebrittain/colourBrightness.js - cptstarling

4
这里是我的翻译尝试:
(function ($) {
    $.fn.contrastingText = function () {
        var el = this,
            transparent;
        transparent = function (c) {
            var m = c.match(/[0-9]+/g);
            if (m !== null) {
                return !!m[3];
            }
            else return false;
        };
        while (transparent(el.css('background-color'))) {
            el = el.parent();
        }
        var parts = el.css('background-color').match(/[0-9]+/g);
        this.lightBackground = !!Math.round(
            (
                parseInt(parts[0], 10) + // red
                parseInt(parts[1], 10) + // green
                parseInt(parts[2], 10) // blue
            ) / 765 // 255 * 3, so that we avg, then normalize to 1
        );
        if (this.lightBackground) {
            this.css('color', 'black');
        } else {
            this.css('color', 'white');
        }
        return this;
    };
}(jQuery));

然后使用它:
var t = $('#my-el');
t.contrastingText();

这将立即根据需要使文本变为黑色或白色。要处理图标:

if (t.lightBackground) {
    iconSuffix = 'black';
} else {
    iconSuffix = 'white';
}

然后每个图标看起来可能像'save' + iconSuffix + '.jpg'
请注意,这在任何容器溢出其父级的情况下都不起作用(例如,如果CSS高度为0且溢出不隐藏)。要使其正常工作需要更复杂的操作。

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