Skip to content

Instantly share code, notes, and snippets.

@samermurad
Last active January 15, 2026 17:07
Show Gist options
  • Select an option

  • Save samermurad/54ca688b977632350c75417aa80485b0 to your computer and use it in GitHub Desktop.

Select an option

Save samermurad/54ca688b977632350c75417aa80485b0 to your computer and use it in GitHub Desktop.
Very basic Typescript Color class, allows init with HexString, HexNumber, RGB and RGBA numbers array
class Color implements Iterable<any>{
private _hex: string;
private _rgba: RGBA;
get hex(): string {
return this._hex;
}
get rgba(): RGBA {
return this._rgba
}
constructor(input: ColorInput) {
if (input === undefined) throw new Error('ColorInput must be a supported type: (HexString/HexNumber/RGB/RGBA/)');
if (typeof input === 'string') {
this._hex = Color.normalizeHexString(input);
this._rgba = Color.hexStringToComponents(this._hex)
} else if (typeof input === 'number') {
this._rgba = Color.hexNumberToComponents(input);
this._hex = Color.componentsToHexString(this._rgba);
} else if (Array.isArray(input)) {
if (!(input.length === 4 || input.length === 3)) throw new Error('must use RGB | RGBA as array input');
this._hex = Color.componentsToHexString(input);
this._rgba = Color.hexStringToComponents(this._hex);
} else if (typeof input === 'object') {
// defaults in invalid pink color
const { r = 1, g = 0, b = 0.7647058823529411, a = 1 } = input;
this._hex = Color.componentsToHexString([r,g,b,a]);
this._rgba = Color.hexStringToComponents(this._hex)
} else {
this._hex = Color.normalizeHexString('#ff00c3a');
this._rgba = Color.hexStringToComponents(this._hex);
}
}
static normalizeHexString(hex: string): HexString {
const hexNoPound = hex.replace('#', '');
if (hexNoPound.length < 2 || hexNoPound.length > 8) throw new Error('Invalid hex value: ' + hex);
let fixHex = '';
switch (hexNoPound.length) {
case 2:
fixHex = `${hexNoPound}${hexNoPound}${hexNoPound}`;
break;
case 3: // 0xffff => 0xff_ff_ff
case 4: // 0xffff => 0xff_ff_ff_ff
fixHex = `${hexNoPound.split('').map(f => `${f}${f}`).join('')}`;
break;
case 5: {// 0xf_f_f_f_f
const [r1, r2, g1, g2, b] = hexNoPound as any;
fixHex = `${r1}${r2}${g1}${g2}${b}${b}`;
break;
}
case 6:
case 8:
fixHex = hexNoPound // string is correct;
/* no op*/
break;
case 7: {// 0xf_f_f_f_f_f_f
const [r1, r2, g1, g2, b1, b2, a] = hexNoPound as any;
fixHex = `${r1}${r2}${g1}${g2}${b1}${b2}${a}${a}`;
break;
}
}
if (isNaN(parseInt(fixHex, 16))) throw new Error(fixHex + ' is not a hex number.');
return `#${fixHex}` as HexString;
}
static hexStringToComponents(hexStr: string) : RGBA {
const normalizedHex = Color.normalizeHexString(hexStr).replace('#', '');
const hex = parseInt(normalizedHex, 16);
if (isNaN(hex)) throw new Error(hex + ' is not a hex number.');
let rgba = [1, 1, 1, 1]; // r, g, b, a
const size = (normalizedHex.length / 2);
for (let i = size - 1; i >= 0; i--)
rgba[(size - 1 - i)] = ((hex >> (i * 8)) & 0xff) / 255.0; // 24 / 16 / 8 / 0;
const [r, g, b, a] = rgba;
return [r, g, b, a];
}
static hexNumberToComponents(hex: number): RGBA {
return Color.hexStringToComponents(hex.toString(16));
}
static componentsToHexString(components: RGB | RGBA): HexString {
let str = '';
for (let i = 0; i < components.length; i++) {
let component = components[i];
// normalize component value
if (component <= 0) component = 0;
else if (component <= 1) component = component * 255.0
else if (component > 1) component = Math.min(component, 255);
component = Math.round(component);
// add it to string as hex
str += component.toString(16).padStart(2, '0');
}
return Color.normalizeHexString(str);
}
valueOf() {
return this._hex;
}
toString() {
return this._hex;
}
[Symbol.iterator](): Iterator<number> {
let currentIndex = 0;
const rgba = this.rgba;
const size = rgba.length;
return {
next(): IteratorResult<number> {
if (currentIndex < size) {
const nextData = currentIndex++
return {
value: rgba[nextData],
done: false,
}
} else {
return { done: true, value: undefined };
}
}
}
}
}
try {
console.log(new Color('#f'));
} catch (error) {
console.log(error.message);
}
try {
console.log(new Color('#rr'));
} catch (error) {
console.log(error.message);
}
console.log(new Color('#ff'));
console.log(new Color('#ffa'));
console.log(new Color('#f01a'));
console.log(new Color('#fd01b'));
console.log(new Color('#68ff46'));
console.log(new Color('#405c91a'));
console.log(new Color('#405c91aa'));
try {
console.log(new Color('#405c91aae'));
} catch (error) {
console.log(error.message);
}
console.log(new Color('#ff00c3'));
console.log(new Color('#ff00c3a'));
console.log(new Color('#ff00c3a'));
console.log(new Color('#ff00c3a'));
console.log(new Color([1, 0, 0.7647058823529411, 0.6666666666666666]));
console.log(new Color([1, 0, 0.7647058823529411]));
console.log(new Color([255, 0, 255 * 0.7647058823529411, 255 * 0.6666666666666666]));
console.log(new Color(0xff00c3a));
console.log(new Color(0xffaacca));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment