634 lines
18 KiB
JavaScript
634 lines
18 KiB
JavaScript
|
|
|
||
|
|
class ColorPicker {
|
||
|
|
constructor(wrapper) {
|
||
|
|
this.pointerDrag = false;
|
||
|
|
this.hueDrag = false;
|
||
|
|
this.opacityDrag = false;
|
||
|
|
|
||
|
|
this.currentSaturation = 0;
|
||
|
|
this.currentValue = 0;
|
||
|
|
this.currentHue = 0;
|
||
|
|
this.currentAlpha = 1;
|
||
|
|
this.currentX;
|
||
|
|
this.currentY;
|
||
|
|
this.initialX;
|
||
|
|
this.initialY;
|
||
|
|
this.xOffset = 0;
|
||
|
|
this.yOffset = 0;
|
||
|
|
// this.element = wrapper;
|
||
|
|
this.wrapper = createElement("div", {
|
||
|
|
classList: ["amber-color-picker"]
|
||
|
|
});
|
||
|
|
this.preview = createElement("div", {
|
||
|
|
classList: ["neo-preview"]
|
||
|
|
});
|
||
|
|
this.input = createElement("input", {
|
||
|
|
|
||
|
|
});
|
||
|
|
this.dialog = createElement("div", {
|
||
|
|
classList: ["amber-palette"],
|
||
|
|
css: {
|
||
|
|
display: "none"
|
||
|
|
}
|
||
|
|
});
|
||
|
|
this.inner = createElement("div", {
|
||
|
|
classList: ["amber-inner"]
|
||
|
|
});
|
||
|
|
this.palette_area = createElement("div", {
|
||
|
|
classList: ["amber-palette-area"]
|
||
|
|
});
|
||
|
|
this.palette_pointer = createElement("div", {
|
||
|
|
|
||
|
|
});
|
||
|
|
this.hue_area = createElement("div", {
|
||
|
|
classList: ["amber-hue-area"]
|
||
|
|
});
|
||
|
|
this.hue_pointer = createElement("div", {
|
||
|
|
|
||
|
|
});
|
||
|
|
this.alpha_area = createElement("div", {
|
||
|
|
classList: ["amber-alpha-area"]
|
||
|
|
});
|
||
|
|
this.alpha_pointer = createElement("div", {
|
||
|
|
|
||
|
|
});
|
||
|
|
this.desc = createElement("div", {
|
||
|
|
innerHTML: "rgb(0,0,0), rgba(0,0,0,0) 등 css 에서 지원하는 색상 형식 입력 가능",
|
||
|
|
css: {
|
||
|
|
"color":"#A0A0A0",
|
||
|
|
"font-size":"9px",
|
||
|
|
"line-height":"16px",
|
||
|
|
"padding": "4px"
|
||
|
|
}
|
||
|
|
});
|
||
|
|
this.recentcolor = createElement("div", {
|
||
|
|
classList: ["amber-recent-color"]
|
||
|
|
});
|
||
|
|
this.palette_area.appendChild(this.palette_pointer);
|
||
|
|
this.hue_area.appendChild(this.hue_pointer);
|
||
|
|
this.alpha_area.appendChild(this.alpha_pointer);
|
||
|
|
this.inner.appendChild(this.palette_area);
|
||
|
|
this.inner.appendChild(this.hue_area);
|
||
|
|
this.inner.appendChild(this.alpha_area);
|
||
|
|
this.inner.appendChild(this.recentcolor);
|
||
|
|
this.dialog.appendChild(this.inner);
|
||
|
|
this.dialog.appendChild(this.desc);
|
||
|
|
this.wrapper.appendChild(this.input);
|
||
|
|
this.wrapper.appendChild(this.preview);
|
||
|
|
this.wrapper.appendChild(this.dialog);
|
||
|
|
for (let j = 0; j < wrapper.attributes.length; j++) {
|
||
|
|
const attr = wrapper.attributes[j];
|
||
|
|
if (attr.name == "class") continue;
|
||
|
|
this.input.setAttribute(attr.name, attr.value);
|
||
|
|
}
|
||
|
|
wrapper.parentNode.replaceChild(this.wrapper, wrapper);
|
||
|
|
this.input.addEventListener("change", (evt) => {
|
||
|
|
this.procEvent(evt, true);
|
||
|
|
});
|
||
|
|
this.input.addEventListener("keyup", (evt) => {
|
||
|
|
this.procEvent(evt);
|
||
|
|
});
|
||
|
|
this.input.addEventListener("focus", (evt) => {
|
||
|
|
this.openDialog();
|
||
|
|
});
|
||
|
|
this.palette_area.addEventListener("mousedown", (evt) => {
|
||
|
|
this.pointerDrag = true;
|
||
|
|
this.movePointer(evt);
|
||
|
|
});
|
||
|
|
this.hue_area.addEventListener("mousedown", (evt) => {
|
||
|
|
this.hueDrag = true;
|
||
|
|
this.moveHue(evt);
|
||
|
|
});
|
||
|
|
this.alpha_area.addEventListener("mousedown", (evt) => {
|
||
|
|
this.opacityDrag = true;
|
||
|
|
this.moveAlpha(evt);
|
||
|
|
});
|
||
|
|
window.addEventListener("mouseup", (evt) => {
|
||
|
|
this.pointerDrag = false;
|
||
|
|
this.hueDrag = false;
|
||
|
|
this.opacityDrag = false;
|
||
|
|
});
|
||
|
|
window.addEventListener("mousemove", (evt) => {
|
||
|
|
this.movePointer(evt);
|
||
|
|
this.moveHue(evt);
|
||
|
|
this.moveAlpha(evt);
|
||
|
|
});
|
||
|
|
this.init();
|
||
|
|
}
|
||
|
|
|
||
|
|
init() {
|
||
|
|
this.checkFocusLeave(() => {
|
||
|
|
this.dialog.style.setProperty('display', 'none');
|
||
|
|
this.addPaletteColor(this.input.value);
|
||
|
|
});
|
||
|
|
this.regex = /[ㅁㅠㅊㅇㄷㄹ]/g;
|
||
|
|
this.replacementMap = {
|
||
|
|
'ㅁ': 'a',
|
||
|
|
'ㅠ': 'b',
|
||
|
|
'ㅊ': 'c',
|
||
|
|
'ㅇ': 'd',
|
||
|
|
'ㄷ': 'e',
|
||
|
|
'ㄹ': 'f'
|
||
|
|
};
|
||
|
|
this.setColor(1);
|
||
|
|
this.getSavedPalette();
|
||
|
|
}
|
||
|
|
|
||
|
|
getSavedPalette() {
|
||
|
|
const d = localStorage.getItem('amber-palette');
|
||
|
|
if (d) {
|
||
|
|
try {
|
||
|
|
this.saved_palette = JSON.parse(d);
|
||
|
|
this.prepareSavedPalette();
|
||
|
|
} catch(ex) {
|
||
|
|
console.error(ex);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
this.saved_palette = [];
|
||
|
|
localStorage.setItem('amber-palette', JSON.stringify([]));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
addPaletteColor(color) {
|
||
|
|
const index = this.saved_palette.indexOf(color.toLowerCase());
|
||
|
|
if (index > -1) {
|
||
|
|
this.saved_palette.splice(index, 1);
|
||
|
|
}
|
||
|
|
this.saved_palette.unshift(color.toLowerCase());
|
||
|
|
if (this.saved_palette.length > 45) {
|
||
|
|
this.saved_palette.pop();
|
||
|
|
}
|
||
|
|
localStorage.setItem('amber-palette', JSON.stringify(this.saved_palette));
|
||
|
|
}
|
||
|
|
|
||
|
|
prepareSavedPalette() {
|
||
|
|
this.recentcolor.innerHTML = "";
|
||
|
|
for(const e of this.saved_palette) {
|
||
|
|
const col = createElement("div", {
|
||
|
|
css: {
|
||
|
|
"--background": e
|
||
|
|
},
|
||
|
|
attributes: {
|
||
|
|
title: e
|
||
|
|
}
|
||
|
|
});
|
||
|
|
col.addEventListener("click", (evt) => {
|
||
|
|
this.input.value = e;
|
||
|
|
this.setColor(1);
|
||
|
|
});
|
||
|
|
this.recentcolor.appendChild(col);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
openDialog() {
|
||
|
|
this.dialog.style.setProperty('display', 'flex');
|
||
|
|
this.getSavedPalette();
|
||
|
|
}
|
||
|
|
|
||
|
|
movePointer(evt) {
|
||
|
|
if (this.pointerDrag) {
|
||
|
|
evt.preventDefault();
|
||
|
|
let rect = this.palette_area.getBoundingClientRect();
|
||
|
|
let cstyle = getComputedStyle(this.palette_area);
|
||
|
|
let newX = evt.clientX - rect.left;
|
||
|
|
let newY = evt.clientY - rect.top;
|
||
|
|
newX = Math.max(0, Math.min(parseInt(cstyle.width), newX));
|
||
|
|
newY = Math.max(0, Math.min(parseInt(cstyle.height), newY));
|
||
|
|
|
||
|
|
this.currentSaturation = newX / parseInt(cstyle.width);
|
||
|
|
this.currentValue = 1 - (newY / parseInt(cstyle.height));
|
||
|
|
|
||
|
|
const newRgb = this.hsvToRgba(this.currentHue, this.currentSaturation, this.currentValue);
|
||
|
|
|
||
|
|
this.input.value = this.rgbToHex(newRgb.r, newRgb.g, newRgb.b) + (Math.max(0, Math.min(255, Math.floor(this.currentAlpha * 255)))).toString(16).padStart(2, '0');
|
||
|
|
this.setColor();
|
||
|
|
this.currentX = newX;
|
||
|
|
this.currentY = newY;
|
||
|
|
this.setTranslate(this.currentX, this.currentY, this.palette_pointer);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
moveHue(evt) {
|
||
|
|
if (this.hueDrag) {
|
||
|
|
evt.preventDefault();
|
||
|
|
let rect = this.hue_area.getBoundingClientRect();
|
||
|
|
let cstyle = getComputedStyle(this.hue_area);
|
||
|
|
let newY = evt.clientY - rect.top;
|
||
|
|
const my = parseInt(cstyle.height);
|
||
|
|
newY = Math.max(0, Math.min(my, newY));
|
||
|
|
|
||
|
|
this.currentHue = (1 - (newY / my)) * 360;
|
||
|
|
|
||
|
|
const newRgb = this.hsvToRgba(this.currentHue, this.currentSaturation, this.currentValue);
|
||
|
|
|
||
|
|
this.input.value = this.rgbToHex(newRgb.r, newRgb.g, newRgb.b) + (Math.max(0, Math.min(255, Math.floor(this.currentAlpha * 255)))).toString(16).padStart(2, '0');
|
||
|
|
|
||
|
|
this.setColor();
|
||
|
|
this.setTranslateY(newY, this.hue_pointer);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
moveAlpha(evt) {
|
||
|
|
if (this.opacityDrag) {
|
||
|
|
evt.preventDefault();
|
||
|
|
let rect = this.alpha_area.getBoundingClientRect();
|
||
|
|
let cstyle = getComputedStyle(this.alpha_area);
|
||
|
|
let newY = evt.clientY - rect.top;
|
||
|
|
const my = parseInt(cstyle.height);
|
||
|
|
newY = Math.max(0, Math.min(my, newY));
|
||
|
|
|
||
|
|
this.currentAlpha = 1 - Math.min(1, Math.max(0, newY / my));
|
||
|
|
|
||
|
|
const newRgb = this.hsvToRgba(this.currentHue, this.currentSaturation, this.currentValue);
|
||
|
|
|
||
|
|
this.input.value = this.rgbToHex(newRgb.r, newRgb.g, newRgb.b) + (Math.max(0, Math.min(255, Math.floor(this.currentAlpha * 255)))).toString(16).padStart(2, '0');
|
||
|
|
|
||
|
|
this.setColor();
|
||
|
|
this.setTranslateY(newY, this.alpha_pointer);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
setTranslate(xPos, yPos, el) {
|
||
|
|
el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;
|
||
|
|
}
|
||
|
|
|
||
|
|
setTranslateY(yPos, el) {
|
||
|
|
el.style.transform = `translateY(${yPos}px)`;
|
||
|
|
}
|
||
|
|
|
||
|
|
checkFocusLeave(callback) {
|
||
|
|
const self = this;
|
||
|
|
function isFocusInside(elem) {
|
||
|
|
return self.wrapper.contains(elem == undefined ? document.activeElement : elem);
|
||
|
|
}
|
||
|
|
|
||
|
|
let wasFocusInside = isFocusInside();
|
||
|
|
|
||
|
|
document.addEventListener('focusin', checkFocus);
|
||
|
|
document.addEventListener('mousedown', checkFocus);
|
||
|
|
document.addEventListener('touchstart', checkFocus);
|
||
|
|
|
||
|
|
function checkFocus(event) {
|
||
|
|
setTimeout(() => {
|
||
|
|
const isFocusInsideNow = isFocusInside(event.target);
|
||
|
|
if (wasFocusInside && !isFocusInsideNow) {
|
||
|
|
callback();
|
||
|
|
}
|
||
|
|
|
||
|
|
wasFocusInside = isFocusInsideNow;
|
||
|
|
}, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
return function cleanup() {
|
||
|
|
document.removeEventListener('focusin', checkFocus);
|
||
|
|
document.removeEventListener('mousedown', checkFocus);
|
||
|
|
document.removeEventListener('touchstart', checkFocus);
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
setColor(c) {
|
||
|
|
const val = this.procInput(this.input.value);
|
||
|
|
if (val) {
|
||
|
|
if (c) {
|
||
|
|
const tar = this.rgbToHsv(val.r, val.g, val.b);
|
||
|
|
this.currentHue = tar.h;
|
||
|
|
this.currentSaturation = tar.s;
|
||
|
|
this.currentValue = tar.v;
|
||
|
|
this.currentAlpha = val.a;
|
||
|
|
}
|
||
|
|
|
||
|
|
const t1 = this.hsvToRgba(this.currentHue, 0, 1, 1);
|
||
|
|
const t2 = this.hsvToRgba(this.currentHue, 1, 1, 1);
|
||
|
|
|
||
|
|
let cstyle = getComputedStyle(this.palette_area);
|
||
|
|
|
||
|
|
if (c) {
|
||
|
|
const hsv = this.rgbToHsv(val.r, val.g, val.b);
|
||
|
|
const mx = parseInt(cstyle.width);
|
||
|
|
const my = parseInt(cstyle.height);
|
||
|
|
const x = Math.min(mx, Math.max(0, (mx * hsv.s)));
|
||
|
|
const y = Math.min(my, Math.max(0, my - (my * hsv.v)));
|
||
|
|
this.setTranslate(x, y, this.palette_pointer);
|
||
|
|
this.setTranslateY(my - ((hsv.h / 360) * my), this.hue_pointer);
|
||
|
|
this.setTranslateY(my - (this.currentAlpha * my), this.alpha_pointer);
|
||
|
|
}
|
||
|
|
|
||
|
|
this.palette_area.style.background = `linear-gradient(to right, rgb(${t1.r},${t1.g},${t1.b}), rgb(${t2.r},${t2.g},${t2.b}))`;
|
||
|
|
this.preview.style.setProperty('--background', `rgba(${val.r},${val.g},${val.b},${val.a.toFixed(2)})`);
|
||
|
|
this.alpha_area.style.setProperty('--background', `linear-gradient(to bottom, rgba(${val.r},${val.g},${val.b},1), rgba(${val.r},${val.g},${val.b},0))`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
procEvent(evt, validate = false) {
|
||
|
|
const cursorPosition = this.input.selectionStart;
|
||
|
|
const val = this.procInput(this.input.value);
|
||
|
|
if (val) {
|
||
|
|
this.setColor(1);
|
||
|
|
}
|
||
|
|
if (validate) {
|
||
|
|
if (val) {
|
||
|
|
this.wrapper.style.setProperty('background', '#FFFFFF');
|
||
|
|
this.wrapper.style.setProperty('border-color', '#F2F2F0');
|
||
|
|
if (val.type == "hex") {
|
||
|
|
if (this.input.value[0] != '#') {
|
||
|
|
this.input.value = `#${this.input.value}`;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
this.addPaletteColor(this.input.value);
|
||
|
|
} else {
|
||
|
|
this.wrapper.style.setProperty('background', '#FFDDDD');
|
||
|
|
this.wrapper.style.setProperty('border-color', '#EEA0A0');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
procInput(input) {
|
||
|
|
input = input.replace(/\s/g, '');
|
||
|
|
|
||
|
|
// RGB and RGBA
|
||
|
|
let match = input.match(/^rgba?\((\d+),(\d+),(\d+)(?:,([\d.]+))?\)$/);
|
||
|
|
if (match) {
|
||
|
|
return {
|
||
|
|
r: parseInt(match[1]),
|
||
|
|
g: parseInt(match[2]),
|
||
|
|
b: parseInt(match[3]),
|
||
|
|
a: match[4] ? parseFloat(match[4]) : 1,
|
||
|
|
type: "rgb/rgba"
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// HSV and HSVA
|
||
|
|
match = input.match(/^hsva?\((\d+),(\d+)%,(\d+)%(?:,([\d.]+))?\)$/);
|
||
|
|
if (match) {
|
||
|
|
let h = parseInt(match[1]) / 360;
|
||
|
|
let s = parseInt(match[2]) / 100;
|
||
|
|
let v = parseInt(match[3]) / 100;
|
||
|
|
let a = match[4] ? parseFloat(match[4]) : 1;
|
||
|
|
return hsvToRgb(h, s, v, a);
|
||
|
|
}
|
||
|
|
|
||
|
|
// HSL and HSLA
|
||
|
|
match = input.match(/^hsla?\((\d+),(\d+)%,(\d+)%(?:,([\d.]+))?\)$/);
|
||
|
|
if (match) {
|
||
|
|
let h = parseInt(match[1]) / 360;
|
||
|
|
let s = parseInt(match[2]) / 100;
|
||
|
|
let l = parseInt(match[3]) / 100;
|
||
|
|
let a = match[4] ? parseFloat(match[4]) : 1;
|
||
|
|
return hslToRgb(h, s, l, a);
|
||
|
|
}
|
||
|
|
|
||
|
|
// HWB
|
||
|
|
match = input.match(/^hwb\((\d+),(\d+)%,(\d+)%(?:,([\d.]+))?\)$/);
|
||
|
|
if (match) {
|
||
|
|
let h = parseInt(match[1]) / 360;
|
||
|
|
let w = parseInt(match[2]) / 100;
|
||
|
|
let b = parseInt(match[3]) / 100;
|
||
|
|
let a = match[4] ? parseFloat(match[4]) : 1;
|
||
|
|
return hwbToRgb(h, w, b, a);
|
||
|
|
}
|
||
|
|
|
||
|
|
// LAB
|
||
|
|
match = input.match(/^lab\((\d+)%,([-\d.]+),([-\d.]+)(?:,([\d.]+))?\)$/);
|
||
|
|
if (match) {
|
||
|
|
let l = parseInt(match[1]);
|
||
|
|
let a = parseFloat(match[2]);
|
||
|
|
let b = parseFloat(match[3]);
|
||
|
|
let alpha = match[4] ? parseFloat(match[4]) : 1;
|
||
|
|
return labToRgb(l, a, b, alpha);
|
||
|
|
}
|
||
|
|
|
||
|
|
// LCH
|
||
|
|
match = input.match(/^lch\((\d+)%,([-\d.]+),([-\d.]+)(?:,([\d.]+))?\)$/);
|
||
|
|
if (match) {
|
||
|
|
let l = parseInt(match[1]);
|
||
|
|
let c = parseFloat(match[2]);
|
||
|
|
let h = parseFloat(match[3]);
|
||
|
|
let alpha = match[4] ? parseFloat(match[4]) : 1;
|
||
|
|
return lchToRgb(l, c, h, alpha);
|
||
|
|
}
|
||
|
|
|
||
|
|
// If no match found, return null
|
||
|
|
return this.procHexRgba(input);
|
||
|
|
}
|
||
|
|
|
||
|
|
procHexRgba(input) {
|
||
|
|
input = input.replace(/^#/, '');
|
||
|
|
if (input.length != 4 && input.length != 8 && input.length != 3 && input.length != 6) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (input.length == 3) {
|
||
|
|
let val = `${input[0]}${input[0]}${input[1]}${input[1]}${input[2]}${input[2]}`;
|
||
|
|
let rgb = this.hexToRgb(val);
|
||
|
|
|
||
|
|
return { r: rgb.r, g: rgb.g, b: rgb.b, a: 1, type: "hex" };
|
||
|
|
} else if (input.length == 4) {
|
||
|
|
let val = `${input[0]}${input[0]}${input[1]}${input[1]}${input[2]}${input[2]}`;
|
||
|
|
let rgb = this.hexToRgb(`${input[0]}${input[0]}${input[1]}${input[1]}${input[2]}${input[2]}`);
|
||
|
|
let alpha = parseInt(`${input[3]}${input[3]}`, 16);
|
||
|
|
|
||
|
|
return { r: rgb.r, g: rgb.g, b: rgb.b, a: alpha / 255, type: "hex" };
|
||
|
|
} else if (input.length == 6) {
|
||
|
|
let rgb = this.hexToRgb(input.substring(0, 6));
|
||
|
|
return { r: rgb.r, g: rgb.g, b: rgb.b, a: 1, type: "hex" };
|
||
|
|
} else if (input.length == 8) {
|
||
|
|
let rgb = this.hexToRgb(input.substring(0, 6));
|
||
|
|
let alpha = parseInt(input.substring(6, 8), 16);
|
||
|
|
return { r: rgb.r, g: rgb.g, b: rgb.b, a: alpha / 255, type: "hex" };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
hsvToRgba(h, s, v, a = 1) {
|
||
|
|
h /= 360;
|
||
|
|
let r, g, b;
|
||
|
|
const i = Math.floor(h * 6);
|
||
|
|
const f = h * 6 - i;
|
||
|
|
const p = v * (1 - s);
|
||
|
|
const q = v * (1 - f * s);
|
||
|
|
const t = v * (1 - (1 - f) * s);
|
||
|
|
|
||
|
|
switch (i % 6) {
|
||
|
|
case 0: r = v, g = t, b = p; break;
|
||
|
|
case 1: r = q, g = v, b = p; break;
|
||
|
|
case 2: r = p, g = v, b = t; break;
|
||
|
|
case 3: r = p, g = q, b = v; break;
|
||
|
|
case 4: r = t, g = p, b = v; break;
|
||
|
|
case 5: r = v, g = p, b = q; break;
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
r: Math.round(r * 255),
|
||
|
|
g: Math.round(g * 255),
|
||
|
|
b: Math.round(b * 255),
|
||
|
|
a: a,
|
||
|
|
type: "hsv/hsva"
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
hslToRgb(h, s, l, a = 1) {
|
||
|
|
let r, g, b;
|
||
|
|
|
||
|
|
if (s === 0) {
|
||
|
|
r = g = b = l; // achromatic
|
||
|
|
} else {
|
||
|
|
const hue2rgb = (p, q, t) => {
|
||
|
|
if (t < 0) t += 1;
|
||
|
|
if (t > 1) t -= 1;
|
||
|
|
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||
|
|
if (t < 1 / 2) return q;
|
||
|
|
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||
|
|
return p;
|
||
|
|
};
|
||
|
|
|
||
|
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||
|
|
const p = 2 * l - q;
|
||
|
|
r = hue2rgb(p, q, h + 1 / 3);
|
||
|
|
g = hue2rgb(p, q, h);
|
||
|
|
b = hue2rgb(p, q, h - 1 / 3);
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
r: Math.round(r * 255),
|
||
|
|
g: Math.round(g * 255),
|
||
|
|
b: Math.round(b * 255),
|
||
|
|
a: a,
|
||
|
|
type: "hsl/hsla"
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
rgbToHsl(r, g, b) {
|
||
|
|
r /= 255, g /= 255, b /= 255;
|
||
|
|
|
||
|
|
let max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||
|
|
let h, s, l = (max + min) / 2;
|
||
|
|
|
||
|
|
if (max == min) {
|
||
|
|
h = s = 0; // achromatic
|
||
|
|
} else {
|
||
|
|
let d = max - min;
|
||
|
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||
|
|
|
||
|
|
switch (max) {
|
||
|
|
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||
|
|
case g: h = (b - r) / d + 2; break;
|
||
|
|
case b: h = (r - g) / d + 4; break;
|
||
|
|
}
|
||
|
|
|
||
|
|
h /= 6;
|
||
|
|
}
|
||
|
|
|
||
|
|
return {h, s, l};
|
||
|
|
}
|
||
|
|
|
||
|
|
hwbToRgb(h, w, b, a = 1) {
|
||
|
|
// First convert to HSL
|
||
|
|
const l = (1 - w - b) / 2;
|
||
|
|
const s = l === 0 || l === 1 ? 0 : (1 - w / (1 - b)) / 2;
|
||
|
|
|
||
|
|
// Then convert HSL to RGB
|
||
|
|
return hslToRgb(h, s, l, a);
|
||
|
|
}
|
||
|
|
|
||
|
|
labToRgb(l, a, b, alpha = 1) {
|
||
|
|
// LAB to XYZ
|
||
|
|
let y = (l + 16) / 116;
|
||
|
|
let x = a / 500 + y;
|
||
|
|
let z = y - b / 200;
|
||
|
|
|
||
|
|
[x, y, z] = [x, y, z].map(value => {
|
||
|
|
if (Math.pow(value, 3) > 0.008856) {
|
||
|
|
return Math.pow(value, 3);
|
||
|
|
} else {
|
||
|
|
return (value - 16 / 116) / 7.787;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// XYZ to RGB
|
||
|
|
let r = 3.2404542 * x - 1.5371385 * y - 0.4985314 * z;
|
||
|
|
let g = -0.9692660 * x + 1.8760108 * y + 0.0415560 * z;
|
||
|
|
let b1 = 0.0556434 * x - 0.2040259 * y + 1.0572252 * z;
|
||
|
|
|
||
|
|
[r, g, b1] = [r, g, b1].map(value => {
|
||
|
|
if (value > 0.0031308) {
|
||
|
|
return 1.055 * Math.pow(value, 1 / 2.4) - 0.055;
|
||
|
|
} else {
|
||
|
|
return 12.92 * value;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
return {
|
||
|
|
r: Math.round(r * 255),
|
||
|
|
g: Math.round(g * 255),
|
||
|
|
b: Math.round(b1 * 255),
|
||
|
|
a: alpha,
|
||
|
|
type: "lab/laba"
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
lchToRgb(l, c, h, alpha = 1) {
|
||
|
|
// Convert LCH to LAB
|
||
|
|
const a = c * Math.cos(h * Math.PI / 180);
|
||
|
|
const b = c * Math.sin(h * Math.PI / 180);
|
||
|
|
|
||
|
|
// Then convert LAB to RGB
|
||
|
|
return labToRgb(l, a, b, alpha);
|
||
|
|
}
|
||
|
|
|
||
|
|
rgbToHsv(r, g, b) {
|
||
|
|
r /= 255, g /= 255, b /= 255;
|
||
|
|
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||
|
|
let h, s, v = max;
|
||
|
|
const d = max - min;
|
||
|
|
s = max === 0 ? 0 : d / max;
|
||
|
|
if (max === min) {
|
||
|
|
h = 0;
|
||
|
|
} else {
|
||
|
|
switch (max) {
|
||
|
|
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||
|
|
case g: h = (b - r) / d + 2; break;
|
||
|
|
case b: h = (r - g) / d + 4; break;
|
||
|
|
}
|
||
|
|
h /= 6;
|
||
|
|
}
|
||
|
|
return { h: h * 360, s: s, v: v };
|
||
|
|
}
|
||
|
|
|
||
|
|
rgbToHex(r, g, b) {
|
||
|
|
r = Math.min(255, Math.max(0, r));
|
||
|
|
g = Math.min(255, Math.max(0, g));
|
||
|
|
b = Math.min(255, Math.max(0, b));
|
||
|
|
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
hexToRgb(hex) {
|
||
|
|
hex = hex.replace(/^#/, '');
|
||
|
|
let bigint = parseInt(hex, 16);
|
||
|
|
let r = (bigint >> 16) & 255;
|
||
|
|
let g = (bigint >> 8) & 255;
|
||
|
|
let b = bigint & 255;
|
||
|
|
return { r, g, b };
|
||
|
|
}
|
||
|
|
|
||
|
|
isValidColor(color) {
|
||
|
|
const s = new Option().style;
|
||
|
|
s.color = color;
|
||
|
|
return s.color !== '';
|
||
|
|
}
|
||
|
|
|
||
|
|
parseColor(color) {
|
||
|
|
const s = new Option().style;
|
||
|
|
s.color = color;
|
||
|
|
return {
|
||
|
|
r: parseInt(s.color.match(/\d+/g)[0]),
|
||
|
|
g: parseInt(s.color.match(/\d+/g)[1]),
|
||
|
|
b: parseInt(s.color.match(/\d+/g)[2]),
|
||
|
|
a: s.color.match(/\d+/g)[3] ? parseFloat(s.color.match(/\d+/g)[3]) : 1
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
document.addEventListener("DOMContentLoaded", (evt) => {
|
||
|
|
document.querySelectorAll('.neo_color').forEach((e) => {
|
||
|
|
new ColorPicker(e);
|
||
|
|
});
|
||
|
|
});
|