Black and White Text Based on Background Image with CSS

Change font color based on background image

I felt I should post an answer to explain what is going on with OPs example.

Simply settings a fill: white on an SVG does not fully reproduce the desired result. OP's example also requires a parent div with mix-blend-mode: difference set as well.

You can actually reproduce this without any SVGs at all. You simply need an element with a color/fill of white, and a parent element with mix-blend-mode: difference. This has to do with how the blending mode of difference works with the color white over a background.

function _Swap() {
document.querySelector(".backgroundDiv").style.background = (document.querySelector(".backgroundDiv").style.background == "rgb(0, 0, 0)") ? "rgb(255, 255, 255)" : "rgb(0, 0, 0)";
}
.backgroundDiv {
background: #FFF;
padding: 4em;
}

.test {
mix-blend-mode: difference;
}

p {
color: #FFF;
}
<div class="backgroundDiv">
<div class="test">
<p>This is a test</p>
</div>
</div>
<input type="button" value="Swap Background Color" onclick="_Swap()">

How to change font color based on background image with javascript?

Explanation


Here is an example showing how to get the average using a canvas rendered to a 1x1-px scale. From there we set the header background and then apply a light or dark class based on the luminance of the aforementioned average.

Notes


Note 1:
There is some extra code to allow for easier visualization, this is commented and can be removed.

Note 2: The below code is not guaranteed to run on all browsers. Please see this thread for more specifics.

Note 3: This code only works with local images and CORS-enabled image URLs due to it's reliance on canvas.drawImage. Please see this for more information.

Example 1:

this example shows the functionality. It uses two image URLs in the JS to toggle between.

/* 

THE TOGGLE ACTION

*/

/* The urls for toggling between */
const urlLight = 'https://images.unsplash.com/photo-1484503793037-5c9644d6a80a?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=930&q=80';
const urlDark = 'https://images.unsplash.com/photo-1517999144091-3d9dca6d1e43?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=627&q=80';
let currentUrl = urlLight;

const toggle = document.getElementById('toggle');

toggle.addEventListener("click", function() {
if (currentUrl === urlLight) {
currentUrl = urlDark;
} else {
currentUrl = urlLight;
}

getImageBrightness(currentUrl, function(brightness) {

const header = document.getElementById("header");
header.style.backgroundImage = `url(${currentUrl}`;

header.classList.remove("dark");
header.classList.remove("light");

console.log(brightness);

if (brightness > 225 / 2) {
header.classList.toggle("dark");

} else {
header.classList.toggle("light");
}
});

});

/*

Important get brightness code

*/

function getImageBrightness(imageSrc, callback) {
var img = document.createElement("img");
img.src = imageSrc;
img.style.display = "none";
img.crossOrigin = "anonymous";
document.body.appendChild(img);

var colorSum = 0;

img.onload = function() {
// create canvas
var canvas = document.createElement("canvas");
canvas.width = this.width;
canvas.height = this.height;

var ctx = canvas.getContext("2d");
ctx.drawImage(this, 0, 0);

var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var data = imageData.data;
var r, g, b, avg;

for (var x = 0, len = data.length; x < len; x += 4) {
r = data[x];
g = data[x + 1];
b = data[x + 2];

avg = Math.floor((r + g + b) / 3);
colorSum += avg;
}

var brightness = Math.floor(colorSum / (this.width * this.height));
callback(brightness);
}
}

getImageBrightness(currentUrl, function(brightness) {

const header = document.getElementById("header");
header.style.backgroundImage = `url(${currentUrl}`;

header.classList.remove("dark");
header.classList.remove("light");

if (brightness > 225 / 2) {
header.classList.toggle("dark");

} else {
header.classList.toggle("light");
}
});
.dark {
color: black;
}

.light {
color: white;
}

* {
text-align: center;
}

#header {
padding: 2rem 0;
font-size: 4rem;
background-size: cover;
margin-bottom: 1rem;
background-position: center center;
}
<div id="header">
Header
</div>

<button id='toggle'>
Toggle Background
</button>

How to put text color as a background image using CSS?

Here is a solution using mix-blend-mode: screen;

https://jsfiddle.net/08rh23tw/

.card-text {
background: white;
color: #000;
height: 100%;
mix-blend-mode: screen;
display: flex;
justify-content: center;
align-items: center;
font-size: 80px;
text-align: center;
padding: 30px;
}

.card {
width: 400px;
height: 600px;
}
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet"/>
<div class="card bg-dark text-white">
<img src="https://images.pexels.com/photos/1005417/pexels-photo-1005417.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=100&w=240"
class="card-img" alt="...">
<div class="card-img-overlay">

<p class="card-text">
Content here.
</p>
</div>
</div>

How to decide font color in white or black depending on background color?

Building on my answer to a similar question.

You need to break the hex code into 3 pieces to get the individual red, green, and blue intensities. Each 2 digits of the code represent a value in hexadecimal (base-16) notation. I won't get into the details of the conversion here, they're easy to look up.

Once you have the intensities for the individual colors, you can determine the overall intensity of the color and choose the corresponding text.

if (red*0.299 + green*0.587 + blue*0.114) > 186 use #000000 else use #ffffff

The threshold of 186 is based on theory, but can be adjusted to taste. Based on the comments below a threshold of 150 may work better for you.


Edit: The above is simple and works reasonably well, and seems to have good acceptance here at StackOverflow. However, one of the comments below shows it can lead to non-compliance with W3C guidelines in some circumstances. Herewith I derive a modified form that always chooses the highest contrast based on the guidelines. If you don't need to conform to W3C rules then I'd stick with the simpler formula above. For an interesting look into the problems with this see Contrast Ratio Math and Related Visual Issues.

The formula given for contrast in the W3C Recommendations (WCAG 2.0) is (L1 + 0.05) / (L2 + 0.05), where L1 is the luminance of the lightest color and L2 is the luminance of the darkest on a scale of 0.0-1.0. The luminance of black is 0.0 and white is 1.0, so substituting those values lets you determine the one with the highest contrast. If the contrast for black is greater than the contrast for white, use black, otherwise use white. Given the luminance of the color you're testing as L the test becomes:

if (L + 0.05) / (0.0 + 0.05) > (1.0 + 0.05) / (L + 0.05) use #000000 else use #ffffff

This simplifies down algebraically to:

if L > sqrt(1.05 * 0.05) - 0.05

Or approximately:

if L > 0.179 use #000000 else use #ffffff

The only thing left is to compute L. That formula is also given in the guidelines and it looks like the conversion from sRGB to linear RGB followed by the ITU-R recommendation BT.709 for luminance.

for each c in r,g,b:
c = c / 255.0
if c <= 0.04045 then c = c/12.92 else c = ((c+0.055)/1.055) ^ 2.4
L = 0.2126 * r + 0.7152 * g + 0.0722 * b

The threshold of 0.179 should not be changed since it is tied to the W3C guidelines. If you find the results not to your liking, try the simpler formula above.

Invert font color based on centered background image - CSS

https://jsfiddle.net/ja63f2zb/

HTML:

<div class="box">
Lorem ipsum.
<span>Lorem ipsum.</span>
</div>

CSS:

.box {
width: 200px;
margin: 200px auto;
height: 400px;
background-color: #CCC;
font-size: 8em;
position: relative;
}
.box span {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
overflow: hidden;
color: #FFF;
}
.box, .box span {
display: -webkit-flex;
display: -moz-flex;
display: -ms-flex;
display: -o-flex;
display: flex;
-ms-align-items: center;
align-items: center;
justify-content: center;
white-space: nowrap;
}

Just don't forget about accessibility

<div class="box">
Lorem ipsum.
<span aria-hidden="true">Lorem ipsum.</span>
</div>

invert text color based on background in css

You could also apply this styling to the element you'd like to invert the colors of:

filter: invert(1);
mix-blend-mode: difference;

It especially works if you need to base the difference on a child or a element further away, not a parent or a close element.

I used it with a custom cursor (the black circle), which made it contrast nicely with the elements behind. Sample pen here: https://codepen.io/m3t4lch7/pen/VwwOKNd

Cursor with inverted colors

Display text color based on background they are on

You can study mix-blend-mode CSS property. Here I've shared just an example. You should dive deep for understanding the blending.

.container {
position: relative;
width: 400px;
}
.wrapper {
display: flex;

}

.left {
background: #463c84;
height: 100vh;
width: 50%;
}
.right {
background: white;
width: 50%;
}

.header {
flex: 1;
position: absolute;
top: 20%;
left: 21%;
background: inherit;
}

.header h1 {
color: #fff;
mix-blend-mode: difference;
}
<div class="container">
<div class="wrapper">
<div class="left"></div>
<div class="right"></div>
</div>
<div class="header">
<h1 class="blend">TEXT WITH INPIRATION</h1>
</div>
</div>

Invert text colour from black to white on grayscale background image

In looking at the page, the designer pulls a visual trick on you. There's actually two separate copies of that text. One inside the image display block and one outside. The one outside lies beneath it, and is black. The one inside is white, but overflow:hidden on the image container constrains it.

By carefully aligning the text to where it gets cut off, you see that effect.

Here's a quick example to demonstrate the technique. (Note: Since it's just a demo I didn't make it play well on mobile.)

JS Bin Example:
https://jsbin.com/loxulil/3/edit?output



Related Topics



Leave a reply



Submit