What is the most efficient way to parse a CSS color in JavaScript?
function parseColor(input) {
var m;
Obviously, the numeric values will be easier to parse than names. So we do those first.
m = input.match(/^#([0-9a-f]{3})$/i)[1];
if( m) {
// in three-character format, each value is multiplied by 0x11 to give an
// even scale from 0x00 to 0xff
return [
parseInt(m.charAt(0),16)*0x11,
parseInt(m.charAt(1),16)*0x11,
parseInt(m.charAt(2),16)*0x11
];
}
That's one. Now for the full six-digit format:
m = input.match(/^#([0-9a-f]{6})$/i)[1];
if( m) {
return [
parseInt(m.substr(0,2),16),
parseInt(m.substr(2,2),16),
parseInt(m.substr(4,2),16)
];
}
And now for rgb()
format:
m = input.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i);
if( m) {
return [m[1],m[2],m[3]];
}
Optionally, you can also add support for rgba
format, and even hsl
/hsla
if you add an HSL2RGB conversion function.
Finally, the named colours.
return ({
"red":[255,0,0],
"yellow":[255,255,0],
// ... and so on. Yes, you have to define ALL the colour codes.
})[input];
And close the function:
}
Actually, I don't know why I bothered writing all that. I just noticed you specified "assuming a major browser", I'm assuming that also means "up-to-date"? If so...
function parseColor(input) {
var div = document.createElement('div'), m;
div.style.color = input;
m = getComputedStyle(div).color.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i);
if( m) return [m[1],m[2],m[3]];
else throw new Error("Colour "+input+" could not be parsed.");
}
An up-to-date browser will convert any given colour to rgb()
format in its computed style. Just get it back, and read it out.
For HTML5 compatible browsers I write a single pixel into a <canvas>
using the specified value, and read back the rgba
quad.
For performance I memoize this function so that repeated calls for the same colour string don't have to perform the canvas operations.
EDIT updated for ES6 and to remove jQuery dependency
EDIT (1j01) added invalid color detection, and a function that supports passing a fallback color
let memoize = function(factory, ctx) {
var cache = {};
return function(key) {
if (!(key in cache)) {
cache[key] = factory.call(ctx, key);
}
return cache[key];
};
};
let colorToRGBA = (function() {
var canvas = document.createElement('canvas');
canvas.width = canvas.height = 1;
var ctx = canvas.getContext('2d');
return memoize(function(col) {
ctx.clearRect(0, 0, 1, 1);
// In order to detect invalid values,
// we can't rely on col being in the same format as what fillStyle is computed as,
// but we can ask it to implicitly compute a normalized value twice and compare.
ctx.fillStyle = '#000';
ctx.fillStyle = col;
var computed = ctx.fillStyle;
ctx.fillStyle = '#fff';
ctx.fillStyle = col;
if (computed !== ctx.fillStyle) {
return; // invalid color
}
ctx.fillRect(0, 0, 1, 1);
return [ ... ctx.getImageData(0, 0, 1, 1).data ];
});
})();
colorToRGBA('white') // [255, 255, 255, 255]
colorToRGBA('blah') // undefined
let colorOrFallbackColorToRGBA = (color, fallbackColor)=> {
// Don't short-circuit getting the fallback RGBA -
// it's already memoized, and we want to show an error
// if the fallback color is invalid even if the main color is valid
var fallbackRGBA = colorToRGBA(fallbackColor);
if (!fallbackRGBA) {
throw new Error(`Invalid fallbackColor ${
fallbackColor != null ? JSON.stringify(fallbackColor) : fallbackColor
}`);
}
return colorToRGBA(color) || fallbackRGBA;
};
colorOrFallbackColorToRGBA('white', 'transparent') // [255, 255, 255, 255]
colorOrFallbackColorToRGBA('blah', 'transparent') // [0, 0, 0, 0]