28

I've written a jQuery plugin that accepts css colors for some of its parameters.

I want to validate them. If it was just a hex or rgb value I could do that with a regular expression, but how do I validate all 147 valid color names without bloating the plugin?

I was wondering if there is some way of attempting to apply a style (maybe with jquery) and then catching an error from the browser if it is not valid?

Edit: powtac and Pantelis came up with a solution, but they both missed edge cases, so I am including a full solution here:

var validateCssColour = function(colour){
    var rgb = $('<div style="color:#28e32a">');     // Use a non standard dummy colour to ease checking for edge cases
    var valid_rgb = "rgb(40, 227, 42)";
    rgb.css("color", colour);
    if(rgb.css('color') == valid_rgb && colour != ':#28e32a' && colour.replace(/ /g,"") != valid_rgb.replace(/ /g,""))
        return false;
    else
        return true;
};
SystemicPlural
  • 5,629
  • 9
  • 49
  • 74

8 Answers8

30

I know this question is fairly old but I came up with a pure javascript solution for checking colors which seems to work in every browser and wanted to share it

This function uses the browser to convert any input string into a CSS color string (if possible)

function getColorCSS(c) {
    const ele = document.createElement("div");
    ele.style.color = c;
    return ele.style.color.replace(/\s+/,'').toLowerCase();
}

UPDATE: The beauty of using the browser itself to render color strings is that it continues to work with newer formats like 8 digit hex (where the last 2 digits represent alpha)...

eight digit hex example


Let's take a look at the function output based on different inputs...

INVALID INPUTS

Basically anytime the browser can't figure out how to render the input string as a color an empty string is returned which makes this tiny function perfect for detecting invalid color strings!

  • redd, #f0gf0g, #1234, f00, rgb(1,2), rgb(1,2,3.0), rgb(1,2,3,4), rgba(100,150,200)

    • . . . chrome
    • . . . firefox
    • . . . internet explorer

 

VALID INPUTS

  • tomato

    • tomato . . . chrome
    • tomato . . . firefox
    • tomato . . . internet explorer

 

  • #f00, #ff0000, rgb(255,0,0)

    • rgb(255,0,0) . . . chrome
    • rgb(255,0,0) . . . firefox
    • rgb(255,0,0) . . . internet explorer

 

  • rgba(255,0,0,1.0), rgba(255,0,0,100)

    • rgb(255,0,0) . . . chrome converts rgba to rgb anytime alpha is 1.0 or greater since it's fully opaque anyway (probably for performance)
    • rgb(255,0,0) . . . firefox does the same thing as chrome
    • rgba(255,0,0,1) . . . internet explorer converts the alpha param from 1.0 or greater to 1

 

  • rgba(0,255,0,0.5)

    • rgba(0,255,0,0.498039) . . . chrome returns something different than the other browsers (possibly trading accuracy for performance)
    • rgba(0,255,0,0.5) . . . firefox
    • rgba(0,255,0,0.5) . . . internet explorer

 

  • rgba(0,0,255,0.0), rgba(0,0,255,-100)

    • rgba(0,0,255,0) . . . chrome converts the alpha param from 0.0 or lower to 0
    • rgba(0,0,255,0) . . . firefox does the same
    • rgba(0,0,255,0) . . . internet explorer does the same

 

  • rgba(0,0,0,0), rgba(0,0,0,-100)

    • rgba(0,0,0,0) . . . chrome
    • transparent . . . firefox converts only this one rgba instance with all parameters set to 0 to the word transparent (strange)
    • rgba(0,0,0,0) . . . internet explorer

 

  • hsl(180,50%,50%)

    • rgb(63,191,191) . . . chrome
    • ff rgb(63,191,191) . . . firefox
    • ie hsl(180,50%,50%) . . . internet explorer

 

  • hsl(x,x,0%)

    • rgb(0,0,0) . . . chrome
    • rgb(0,0,0) . . . firefox
    • hsl(0,0%,0%) . . . internet explorer converts any hsl representation of black to this

 

  • hsl(x,x,100%)

    • rgb(255,255,255) . . . chrome
    • rgb(255,255,255) . . . firefox
    • hsl(0,0%,100%) . . . internet explorer converts any hsl representation of white to this

 

  • hsla(180,50%,50%,1.0), hsla(180,50%,50%,100)

    • rgba(63,191,191,1) . . . chrome
    • rgba(63,191,191,1) . . . firefox
    • hsla(180,50%,50%,1) . . . internet explorer

 

  • hsla(180,50%,50%,0.5)

    • rgba(63,191,191,0.498039) . . . chrome
    • rgba(63,191,191,0.5) . . . firefox
    • hsla(180,50%,50%,0.5) . . . internet explorer

 

  • hsla(0,0%,0%,0.0), hsla(0,0%,0%,-100)

    • rgba(0,0,0,0) . . . chrome
    • transparent . . . firefox is consistent but this conversion still seems strange
    • hsla(180,50%,50%,0) . . . internet explorer

Wow, I can hardly believe I just spent 2 hours testing that function in different browsers!

I guess I may as well demo using the function while I'm at it...

function getColorCSS(c) {
    var ele = document.createElement("div");
    ele.style.color = c;
    return ele.style.color.split(/\s+/).join('').toLowerCase();
}

function isColorValid(c) {
    var s = getColorCSS(c);
    return (s) ? true : false;
}

function isColorTransparent(c) {
    var s = getColorCSS(c);
    return (
        s === "transparent" || 
        s.substring(0,4) === "rgba" && +s.replace(/^.*,(.+)\)/,'$1') <= 0 ||
        s.substring(0,4) === "hsla" && +s.replace(/^.*,(.+)\)/,'$1') <= 0
    );
}

function isColorWhite(c) {
    var s = getColorCSS(c);
    return [
        "white",
        "rgb(255,255,255)",
        "rgba(255,255,255,1)",
        "hsl(0,0%,100%)",
        "hsla(0,0%,100%,1)"
    ].indexOf(s) > -1;
}

function isColorBlack(c) {
    var s = getColorCSS(c);
    return [
        "black",
        "rgb(0,0,0)",
        "rgba(0,0,0,1)",
        "hsl(0,0%,0%)",
        "hsla(0,0%,0%,1)"
    ].indexOf(s) > -1;
}

function checkColorString() {
  var c = document.getElementById("c").value;
  
  if (c === "") {
    document.getElementsByTagName("body")[0].style.backgroundColor = 'transparent';
    document.getElementById("result").innerHTML = '';
  }
  else if (isColorValid(c)) {
    if (isColorTransparent(c)) {
      document.getElementsByTagName("body")[0].style.backgroundColor = 'transparent';
      document.getElementById("result").innerHTML = '<span style="color:#dadada;">TRANSPARENT</span>';
    }
    else if (isColorWhite(c)) {
      document.getElementsByTagName("body")[0].style.backgroundColor = getColorCSS(c);
      document.getElementById("result").innerHTML = '<span style="color:black;">WHITE</span>';
    }
    else if (isColorBlack(c)) {
      document.getElementsByTagName("body")[0].style.backgroundColor = getColorCSS(c);
      document.getElementById("result").innerHTML = '<span style="color:white;">BLACK</span>';
    }
    else {
      document.getElementsByTagName("body")[0].style.backgroundColor = getColorCSS(c);
      document.getElementById("result").innerHTML = getColorCSS(c);
    }
  }
  else {
    document.getElementsByTagName("body")[0].style.backgroundColor = 'transparent';
    document.getElementById("result").innerHTML = '<span style="font-size:42px;color:#dadada;">&#9785</span>';
  }
}

var eventList = ["change", "keyup", "paste", "input", "propertychange"];
for(event of eventList) {
    document.getElementById("c").addEventListener(event, function() {
      checkColorString();
    });
}
#c {
  width: 300px;
  padding: 6px;
  font-family: courier;
}

#result {
  font-family: courier;
  font-size: 24px;
  padding: 12px 6px;
}
<input id="c" placeholder="Enter any valid CSS color..."></input>
<div id="result"></div>
DaveAlger
  • 2,376
  • 25
  • 24
  • one thing I don't understand... why do `str.split(/\s+/).join("")` instead of simply `str.replace(/\s+/g, "")` – Valen Dec 16 '18 at 23:38
  • and somehow, if anyone really hates to create element every time, you can try `document.head.style.color` (but not if your page does not have head unfortunately), and such: `return [document.head.style.color = "I am color", document.head.style.color][1]` (I do believe replacing the space is not necessary. – Valen Dec 16 '18 at 23:58
  • Good call — I updated the function to do a replace instead of split/join. – DaveAlger Nov 10 '19 at 17:14
  • @Valen — Maybe replacing spaces is overkill but since I can’t control what every browser returns I think it’s better to remove white space to ensure invalid conversions return something falsey – DaveAlger Nov 10 '19 at 17:18
28

All of the solutions posted on this page are incorrect when the string in question is the same colour as the test colour. Granted, you could use a very unlikely choice of colour, but I would prefer to go for 100% success rate.

OP has a single typo in his code (see condition with colon), and does not test for "#28e32a", so that colour will fail, and the regex will collapse whitespace within the colour, so "#28e 32a" would (incorrectly) pass.

In normal JavaScript, this should have 100% success:

function validTextColour(stringToTest) {
    //Alter the following conditions according to your need.
    if (stringToTest === "") { return false; }
    if (stringToTest === "inherit") { return false; }
    if (stringToTest === "transparent") { return false; }

    var image = document.createElement("img");
    image.style.color = "rgb(0, 0, 0)";
    image.style.color = stringToTest;
    if (image.style.color !== "rgb(0, 0, 0)") { return true; }
    image.style.color = "rgb(255, 255, 255)";
    image.style.color = stringToTest;
    return image.style.color !== "rgb(255, 255, 255)";
}

JSFiddle: http://jsfiddle.net/WK_of_Angmar/xgA5C/

Wk_of_Angmar
  • 572
  • 6
  • 14
  • 1
    I like your line of thinking, but this fails for "rgb(0,0,0)" – Simon Sarris Jun 25 '14 at 15:16
  • 1
    Why do you have the last two lines instead of just `return false`? – Simon Sarris Jun 26 '14 at 13:54
  • 1
    That's because I missed out a line (see edit). The last two lines are essentially for the case "rgb(0,0,0)" with or without spaces. In other words, we first assume that the colour the user has given is NOT white and so if the colour of the image has changed, it is a valid input. The last two lines assumes that it might be a valid input but it is white so the colour of the image should change from black to something else. – Wk_of_Angmar Aug 18 '14 at 13:58
  • It should now work for "rgb(0,0,0)" - with and without spaces in between. – Wk_of_Angmar Aug 18 '14 at 14:01
  • This will also return true for `rgb(0, 0, 0` - even though the `)` is missing – Miha Šušteršič Feb 02 '18 at 16:34
  • 1
    still not working when entering `#000` or `black`, your function has racial bugs – Sam Mar 07 '18 at 12:20
14

You could simply set the color to a dummy element and then check if the element's value is something other than white.

colorToTest = 'lime'; // 'lightgray' does not work for IE

$('#dummy').css('backgroundColor', 'white');
$('#dummy').css('backgroundColor', colorToTest);
if ($('#dummy').css('backgroundColor') != 'rgb(255, 255, 255)' || colorToTest == 'white') {
    alert(colorToTest+' is valid');
}
michaellindahl
  • 2,012
  • 5
  • 36
  • 64
powtac
  • 40,542
  • 28
  • 115
  • 170
  • Thanks. I'm going to make one amendment; use a non standard color for the check that can only be represented with a six digit hex or an rgb. Your code would fail for colorToTest = '#fff' and '#ffffff'. Using a non standard color simplifies it. – SystemicPlural Jun 17 '11 at 13:39
  • Also need to include to remove whitespace when checking rgb values. – SystemicPlural Jun 17 '11 at 13:51
  • 1
    A tweak to this is to set both the color and backgroundColor fields to the new colour. If afterwards the two CSS elements are the same, then this is a valid colour. – robbrit Feb 24 '12 at 20:52
7

function test(myColor) {
  var valid = $('#test').css('color');

  $('#test').css('color', myColor);

  if (valid == $('#test').css('color')) alert("INVALID COLOR");
  else {
    alert("VALID");
    $('#test').css('color', valid);
  }
}


test("TATATA");
test("white");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="test">asdasdasdasd</div>

It is hastily written but it works.

Bram Vanroy
  • 27,032
  • 24
  • 137
  • 239
Pantelis
  • 6,086
  • 1
  • 18
  • 21
  • 2
    Fails for `black`... But you could check: `if (color == "black") OK else validation_code` – BrunoLM Jun 17 '11 at 13:11
  • Yes you are 100% correct. It fails because it checks for valid== $('#test').css('color') and black is its default value. It can be fixed with the way you mention. Thanks! – Pantelis Jun 17 '11 at 13:15
  • Thanks. Your idea is the same as powtacs. Only he got there 30 or so seconds before you so I awarded him the credit. It's a great solution. – SystemicPlural Jun 17 '11 at 13:30
1

I'm arriving a little bit late but now I'm using validate-color library on npm

JORGE GARNICA
  • 1,016
  • 2
  • 9
  • 13
0

crazy thought

function isValidCssColor(c){
    // put !! to normalize to boolean
    return [
            document.head.style.color = c,
            document.head.style.color,
            document.head.style.color = null
    ][1]; // split it in 3 lines is fine, see snippet
}

I believe it works (only not when your pages doesn't not have a head element, somehow)

function isValidCssColor(c){
  document.head.style.color = c;
  let result = document.head.style.color;
  document.head.style.color = null;
  return result;
}

console.log(isValidCssColor("asdf"));       // <empty string>
console.log(isValidCssColor("black"));      // black
console.log(isValidCssColor("#abcdee"));    // rgb(171, 205, 238) // this is browser dependent I guess
console.log(isValidCssColor("asssdf"));     // <empty string>
Valen
  • 1,693
  • 1
  • 20
  • 17
0

I think you can use the script from this page that does exactly what you want: you pass a string to it and tries to figure out the color. Ok, it's not exactly what you wanted to do but it should work.

I think that in any case at some point you should do a name lookup (i don't think there is some magic way to determine the hex value from the string "light blue") so just snatch the work some one else has done (the script is by Stoyan Stefanov which is very good at javascript, i've read some good books from him, and if he does a lookup, well, i don't think there is another solution)

Nicola Peluchetti
  • 76,206
  • 31
  • 145
  • 192
0

Short JavaScript version

Inspired by Valen's answer:

v=c=>((s=document.head.style).color=c,q=s.color,s.color='',!!q);
// v('red') => true
// v('reeeed') => false

More readably:

const validColor = c => {
  document.head.style.color = c;
  const q = document.head.style.color;
  document.head.style.color = '';
  return !!q
};
Steve Bennett
  • 114,604
  • 39
  • 168
  • 219