Change text color based on brightness of the covered background area?
Solution 1
Interesting resources for this:
- W3C - Ensure that foreground and background color combinations provide sufficient contrast
- Calculating the Perceived Brightness of a Color
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)
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, 2021Comments
-
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 over 11 yearsYou'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 over 11 yearsYes. 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 over 11 yearsThanks, 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 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 over 11 yearsHow about this? stackoverflow.com/questions/2541481/…
-
Nathan MacInnes over 11 yearsWhat if the background colour is
#808080
!? -
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 almost 9 yearsWhat if the text does not have a background color, but its parent element?
-
Centril over 8 yearsIn 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 over 8 yearsDoes 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 over 8 yearsHello Jørgen, I think the colourBrightness script may serve your purpose: github.com/jamiebrittain/colourBrightness.js
-
Hanna almost 7 yearsI 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 almost 7 years@reggie it's your job to do correct selector to select color and is not related to the question.
-
Luke Robertson over 6 yearsCan 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 over 5 yearsthis one is coo, I would just adjust the yiq to >= 160, worked better for me.
-
B''H Bi'ezras -- Boruch Hashem over 4 yearsdo u rly need jquery just for changing the css?
-
Alex Ball over 4 years@bluejayke no, there are other ways ;-)