Thinking About Colors
const { assign } = Object
const { min, max, floor, round, abs } = Math
// `hslToRgb` and `rgbToHsl` functions from the amazing Raphael library
// by Dmitry Baranovskiy
// https://github.com/DmitryBaranovskiy/raphael/blob/master/raphael.js
export function hslToRgb({ h, s, l, a }) {
let H = h
let S = s
let L = l
s /= 100
l /= 100
let r, g, b, x, c
h = (h % 360) / 60
c = 2 * s * (l < .5 ? l : 1 - l)
x = c * (1 - abs(h % 2 - 1))
r = g = b = l - c / 2;
h = floor(h)
r += [c, x, 0, 0, x, c][h]
g += [x, c, c, x, 0, 0][h]
b += [0, 0, x, c, c, x][h]
r *= 255; g *= 255; b *= 255
r = round(r)
g = round(g)
b = round(b)
return { r, g, b, h: H, s: S, l: L, a }
}
export function rgbToHsl({ r, g, b, a }) {
let R = r
let G = g
let B = b
r /= 255
g /= 255
b /= 255
let h, s, l, w, m, c;
w = max(r, g, b);
m = min(r, g, b);
c = w - m;
h = (c == 0 ? null :
w == r ? (g - b) / c :
w == g ? (b - r) / c + 2 :
(r - g) / c + 4)
h = ((h + 360) % 6) * 60 / 360;
l = (w + m) / 2;
s = (c == 0 ? 0 : l < .5 ?
c / (2 * l) :
c / (2 - 2 * l));
h *= 360
s *= 100
l *= 100
h = round(h)
s = round(s)
l = round(l);
return { h, s, l, r: R, g: G, b: B, a }
}
const fromDec = dec => ({
r: dec >> 16,
g: dec >> 8 & 255,
b: dec & 255
})
const fromHex = hex =>
fromDec(Number('0x' + hex.slice(1)))
const components = def =>
def.match(/[0-9\.]+/g).map(parseFloat)
const fromRgb = rgb => {
const [r, g, b, a] = components(rgb)
return { r, g, b, a: a ?? 1 }
}
const fromHsl = hsl => {
const [h, s, l, a] = components(hsl)
return { h, s, l, a: a ?? 1 }
}
const utils = {
hex: (def, o) => assign(o, rgbToHsl(
fromHex(def)), { a: o.a ?? 1 }
),
rgb: (def, o) => assign(o, rgbToHsl(fromRgb(def))),
hsl: (def, o) => assign(o, hslToRgb(fromHsl(def))),
prop: (def, o) => {
const name = Object.keys(def)[0]
o[name] = def[name]
if (name.match(/[rgb]/)) assign(o, rgbToHsl(o))
if (name.match(/[hsl]/)) assign(o, hslToRgb(o))
return o
}
}
const types = { '#': 'hex', r: 'rgb', h: 'hsl', '[': 'prop' }
const type = str => types[str[0]]
const hx = c => {
const chan = (+c).toString(16)
if (chan.length === 0) return '00'
if (chan.length === 1) return '0' + chan
return chan
};
export function shade(def) {
const o = {
set(def) {
const method = utils[type(String(def))]
if (method != null) method(def, o)
},
hsl: _ => `hsla(${o.h}, ${o.s}%, ${o.l}%, ${o.a})`,
rgb: _ => `rgba(${o.r}, ${o.g}, ${o.b}, ${o.a})`,
hex: _ => `#${hx(o.r)}${hx(o.g)}${hx(o.b)}`
}
o.set(def)
return o;
}
Wrote this today for something at work… it’s not done yet but figured it was worth a post… will probably post finished version tomorrow…
This can be a little golfed since it is just a demo for work and not code on a real product 😀