How can I generate the opposite color according to current color?

I'm trying to create a color opposite of current color. I mean if current color is black, then I need to generate white.

Actually I have a text (the color of this text is dynamic, its color can be made at random). That text is into a div and I need to set the opposite color of that text for the background-color of div. I would like that text be clear in the div (color perspective).

The opposite color means: Dark / Bright

I have the current color of text and I can pass it to this function:

var TextColor = #F0F0F0;    // for example (it is a bright color)

function create_opp_color(current color) {

    // create opposite color according to current color

}

create_opp_color(TextColor); // this should be something like "#202020" (or a dark color)

Is there any idea to create create_opp_color() function?


Solution 1:

UPDATE: Production-ready code on GitHub.


This is how I'd do it:

  1. Convert HEX to RGB
  2. Invert the R,G and B components
  3. Convert each component back to HEX
  4. Pad each component with zeros and output.
function invertColor(hex) {
    if (hex.indexOf('#') === 0) {
        hex = hex.slice(1);
    }
    // convert 3-digit hex to 6-digits.
    if (hex.length === 3) {
        hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
    }
    if (hex.length !== 6) {
        throw new Error('Invalid HEX color.');
    }
    // invert color components
    var r = (255 - parseInt(hex.slice(0, 2), 16)).toString(16),
        g = (255 - parseInt(hex.slice(2, 4), 16)).toString(16),
        b = (255 - parseInt(hex.slice(4, 6), 16)).toString(16);
    // pad each with zeros and return
    return '#' + padZero(r) + padZero(g) + padZero(b);
}

function padZero(str, len) {
    len = len || 2;
    var zeros = new Array(len).join('0');
    return (zeros + str).slice(-len);
}

Example Output:

enter image description here

Advanced Version:

This has a bw option that will decide whether to invert to black or white; so you'll get more contrast which is generally better for the human eye.

function invertColor(hex, bw) {
    if (hex.indexOf('#') === 0) {
        hex = hex.slice(1);
    }
    // convert 3-digit hex to 6-digits.
    if (hex.length === 3) {
        hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
    }
    if (hex.length !== 6) {
        throw new Error('Invalid HEX color.');
    }
    var r = parseInt(hex.slice(0, 2), 16),
        g = parseInt(hex.slice(2, 4), 16),
        b = parseInt(hex.slice(4, 6), 16);
    if (bw) {
        // https://stackoverflow.com/a/3943023/112731
        return (r * 0.299 + g * 0.587 + b * 0.114) > 186
            ? '#000000'
            : '#FFFFFF';
    }
    // invert color components
    r = (255 - r).toString(16);
    g = (255 - g).toString(16);
    b = (255 - b).toString(16);
    // pad each with zeros and return
    return "#" + padZero(r) + padZero(g) + padZero(b);
}

Example Output:

enter image description here

Solution 2:

Simple and elegant.

function invertHex(hex) {
  return (Number(`0x1${hex}`) ^ 0xFFFFFF).toString(16).substr(1).toUpperCase()
}

invertHex('00FF00'); // Returns FF00FF

Solution 3:

Simple way to achieve this with CSS:

mix-blend-mode: difference;
color:white;

Solution 4:

Pure CSS implementation of @Onur's answer bw part.

  <input type="color" oninput="['--r','--g','--b'].forEach((k,i)=>this.nextElementSibling.style.setProperty(k,parseInt(event.target.value.slice(1+i*2,3+i*2),16)))" />
 
  <div style="--r: 0; --g: 0; --b: 0; --c: calc(-1 * ((var(--r) * 0.299 + var(--g) * 0.587 + var(--b) * 0.114) - 186) * 255)">
    <div style="background-color: rgb(var(--r), var(--g), var(--b)); color: rgb(var(--c), var(--c), var(--c))">Test</div>
  </div>