Change text color based on brightness of the covered background area?

Viewed 111433

I am looking for a plugin or technique that changes a text's color or switches between predefined images/icons depending on the average brightness of the covered pixels of its parent's background-image or -color.

If the covered area of it's background is rather dark, make the text white or switch the icons.

Additionally, it'd be great if the script would notice if the parent has no defined background-color or -image and then continue to search for the nearest (from parent element to its parent element..).

What do you think, know about this idea? Is there something similar out there already? Examples?

9 Answers

If you are using ES6, convert hex to RGB then can use this:

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))

By combining the answers [ @alex-ball , @jeremyharris ] I found this to be the best way for me:

        $('.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>

In es6 contrast from a HEX 6-character color string (#123456) can be calculated with this one-liner:

const contrastColor = c=>["black","white"][~~([1,3,5].map(p=>parseInt(c.substr(p,2),16)).reduce((r,v,i)=>[.299,.587,.114][i]*v+r,0)<128)];

Here is broken down, readable version:

const contrastColor = color =>
{
  const lum = [1,3,5].map(pos => //get RGB colors array from the string at positions 1, 3 and 5 (0 = # character)
  {
    // return converted hex value into decimal for each R, G, and B
    return parseInt(color.substr(pos, 2), 16);
  }).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 = Red, Green or Blue value of the color
    // index = current position index in the array
    // y = https://www.w3.org/TR/AERT/#color-contrast

    const y = [.299 /*red*/,.587 /*green*/,.114 /*blue*/][index];
    return result + y * value // return sum of previous results
  }
  , 0 /* result = 0 */);
  
  const isDark = lum < 128;
  const index = ~~isDark; // convert boolean into 0 or 1
  return ["black", "white"][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>

Related