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

102,030

Solution 1

Interesting resources for this:

Here's the W3C algorithm (with JSFiddle demo too):

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>

Solution 2

This article on 24 ways about Calculating Color Contrast might be of interest to you. Ignore the first set of functions because they're wrong, but the YIQ formula will help you determine whether or not to use a light or dark foreground color.

Once you obtain the element's (or ancestor's) background color, you can use this function from the article to determine a suitable foreground color:

function getContrastYIQ(hexcolor){
    hexcolor = hexcolor.replace("#", "");
    var r = parseInt(hexcolor.substr(0,2),16);
    var g = parseInt(hexcolor.substr(2,2),16);
    var b = parseInt(hexcolor.substr(4,2),16);
    var yiq = ((r*299)+(g*587)+(b*114))/1000;
    return (yiq >= 128) ? 'black' : 'white';
}

Solution 3

mix-blend-mode does the trick:

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>

Addition (March 2018): Following, a nice tutorial explaining all different types of modes/implementations: https://css-tricks.com/css-techniques-and-effects-for-knockout-text/

Solution 4

Interesting question. My immediate thought was to invert the color of the background as the text. This involves simply parsing the background and inverting its RGB value.

Something like this: 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+')');

Solution 5

I've found the BackgroundCheck script to be very useful.

It detects the overal brightness of the background (be it a background image or a color), and applies a class to the assigned text-element (background--light or background--dark), dependent on the brightness of the background.

It can be applied to still and moving elements.

(Source)

Share:
102,030
James Cazzetta
Author by

James Cazzetta

Download Pi Network in the App store and mine free Coins as long as it is being developed: 🚀 Use james as invitation code 🚀 Working at LzLabs GmbH. Becoming a Solidity Crypto Developer in my free time. 🚀 ETH to the moon 🌙

Updated on September 07, 2021

Comments

  • James Cazzetta
    James Cazzetta over 2 years

    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?

  • jackwanders
    jackwanders over 11 years
    You'd probably want to desaturate your 'inverted' color by averaging the inverted R,G,B values and setting them equal to each other. However, this solution is getting its base color from a string, and not from the CSS property of the element. To be reliable, the solution would have to dynamically obtain background colors, which usually returns rgb() or rgba() values, but could differ according to browser.
  • jeremyharris
    jeremyharris over 11 years
    Yes. For ease of parsing, I just used a hex value. I updated the fiddle to include grabbing the element's color from the CSS. I updated the fiddle and included a sort of brightness control (I don't know anything about color math so it's probably not truly brightness).
  • James Cazzetta
    James Cazzetta over 11 years
    Thanks, this is really helpful.. This depends on the set background-color.. But do you know how to get the average color of an image by running through each pixel (like in a loop)?
  • James Cazzetta
    James Cazzetta over 11 years
    @jeremyharris This is a very helpful piece of code, but to extend the possibilities, do you know how to get the average color of an image by running through each pixel (like in a loop)? So, instead of grabbing the background-color via CSS we could gain the background-images average color.
  • jeremyharris
    jeremyharris over 11 years
  • Nathan MacInnes
    Nathan MacInnes over 11 years
    What if the background colour is #808080!?
  • jeremyharris
    jeremyharris over 11 years
    @NathanMacInnes it'll still invert it, it just so happens that inverting something right in the middle of the spectrum will result in itself. This code just inverts the color, which comes with its limitations.
  • reggie
    reggie almost 9 years
    What if the text does not have a background color, but its parent element?
  • Centril
    Centril over 8 years
    In es6 you can do this with: 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; }
  • Jørgen Skår Fischer
    Jørgen Skår Fischer over 8 years
    Does this work for background-colors? I've fast-read the script, and cant see it utilizing background-color to check for brightness. Only images.
  • cptstarling
    cptstarling over 8 years
    Hello Jørgen, I think the colourBrightness script may serve your purpose: github.com/jamiebrittain/colourBrightness.js
  • Hanna
    Hanna almost 7 years
    I took this function and expanded it a bit so that you could return two custom colors, rather than always black and white. Note that if the colors are two close together you may still get contrast issues, but this is a good alternative to returning absolute colors jsfiddle.net/1905occv/1
  • Petroff
    Petroff almost 7 years
    @reggie it's your job to do correct selector to select color and is not related to the question.
  • Luke Robertson
    Luke Robertson over 6 years
    Can be shorted to the following, providing you pass it a object :::: const setContrast = rgb => (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000 > 125 ? 'black' : 'white'
  • Arturo
    Arturo over 5 years
    this one is coo, I would just adjust the yiq to >= 160, worked better for me.
  • B''H Bi'ezras -- Boruch Hashem
    B''H Bi'ezras -- Boruch Hashem over 4 years
    do u rly need jquery just for changing the css?
  • Alex Ball
    Alex Ball over 4 years
    @bluejayke no, there are other ways ;-)