UNPKG

quill-easy-color

Version:
352 lines (306 loc) 10.9 kB
import { createBEM } from './bem' const normalizeValue = function (value, max) { value = Math.min(max, Math.max(0, Number.parseFloat(`${value}`))); // Handle floating point rounding errors if (Math.abs(value - (max)) < 0.000_001) { return 1; } // Convert into [0, 1] range if it isn't already return (value % (max)) / Number.parseFloat(max); }; export const validateHSB = (hsb) => { return { h: Math.min(360, Math.max(0, hsb.h)), s: Math.min(100, Math.max(0, hsb.s)), b: Math.min(100, Math.max(0, hsb.b)), a: Math.min(1, Math.max(0, hsb.a)), }; }; export const HEXtoRGB = (hex) => { hex = hex.startsWith('#') ? hex.slice(1) : hex; const r = Number.parseInt(hex.slice(0, 2), 16); const g = Number.parseInt(hex.slice(2, 4), 16); const b = Number.parseInt(hex.slice(4, 6), 16); const a = Number((Number.parseInt(hex.slice(6, 8) || 'ff', 16) / 255).toFixed(2)); return { r, g, b, a }; }; export const RGBtoHSB = (rgb) => { let { r, g, b, a } = rgb; r = normalizeValue(r, 255); g = normalizeValue(g, 255); b = normalizeValue(b, 255); const max = Math.max(r, g, b); const min = Math.min(r, g, b); let h; const v = max; const d = max - min; const s = max === 0 ? 0 : d / max; if (max === min) { h = 0; // achromatic } 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 * 100, b: v * 100, a }; }; export const HSBtoRGB = (hsb) => { let { h, s, b, a } = hsb; h = normalizeValue(h, 360) * 6; s = normalizeValue(s, 100); b = normalizeValue(b, 100); const i = Math.floor(h); const f = h - i; const p = b * (1 - s); const q = b * (1 - f * s); const t = b * (1 - (1 - f) * s); const mod = i % 6; const r = [b, q, p, p, t, b][mod]; const g = [t, b, b, q, p, p][mod]; const v = [p, p, t, b, b, q][mod]; return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(v * 255), a, }; }; export const RGBtoHEX = (rgb) => { const hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16), Math.round(rgb.a * 255).toString(16)]; for (const key in hex) { if (hex[key].length === 1) { hex[key] = `0${hex[key]}`; } } return hex.join(''); }; export const HSBtoHEX = (hsb) => RGBtoHEX(HSBtoRGB(hsb)); export const RGBStringtoRGB = (colorString) => { colorString = colorString.replace(/\s+/g, ''); const match = colorString.match(/rgba?\((\d+),(\d+),(\d+)(?:,([\d.]+))?\)/); if (!match) { return { a: 1, r: 0, g: 0, b: 0 } } const r = parseInt(match[1], 10); const g = parseInt(match[2], 10); const b = parseInt(match[3], 10); const a = match[4] ? parseFloat(match[4]) : 1; return { a, r, g, b }; } export const isLightColor = (rgb) => { const { r, g, b } = rgb; const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255; return luminance > 0.5; }; export const colortoRGB = (color) => { const span = document.createElement('span'); span.style.backgroundColor = color; return RGBStringtoRGB(span.style.backgroundColor); } export const createColorPicker = (options = {}) => { const contentWidth = 230; const contentHeight = 150; const handleSizeSec = 10; let hsbValue = RGBtoHSB(HEXtoRGB(options.color || '#ff0000')); const bem = createBEM('color-picker'); const root = document.createElement('div'); root.classList.add(bem.b()); const content = document.createElement('div'); content.classList.add(bem.be('content')); const colorSelector = document.createElement('div'); colorSelector.classList.add(bem.be('selector')); const colorBackground = document.createElement('div'); colorBackground.classList.add(bem.be('background')); colorSelector.appendChild(colorBackground); const colorHandle = document.createElement('div'); colorHandle.classList.add(bem.be('background-handle')); colorBackground.appendChild(colorHandle); const colorAlpha = document.createElement('div'); colorAlpha.classList.add(bem.be('alpha')); const alphaBg = document.createElement('div'); alphaBg.classList.add(bem.be('alpha-bg')); const alphaHandle = document.createElement('div'); alphaHandle.classList.add(bem.be('alpha-handle')); colorAlpha.appendChild(alphaBg); colorAlpha.appendChild(alphaHandle); const colorHue = document.createElement('div'); colorHue.classList.add(bem.be('hue')); const colorHueHandle = document.createElement('div'); colorHueHandle.classList.add(bem.be('hue-handle')); colorHue.appendChild(colorHueHandle); const action = document.createElement('div'); action.classList.add(bem.be('action')); const [colorRInput, colorGInput, colorBInput, colorAInput] = (['r', 'g', 'b', 'a']).map((key) => { const item = document.createElement('div'); item.classList.add(bem.be('action-item'), key); const label = document.createElement('label'); label.textContent = key.toUpperCase(); const colorInput = document.createElement('input'); colorInput.classList.add(bem.be('input')); colorInput.addEventListener('input', () => { colorInput.value = colorInput.value.replaceAll(/[^0-9]/g, ''); }); colorInput.addEventListener('change', () => { let value = Math.round(Number(colorInput.value)); if (key === 'a') { value = value / 100; } const result = validateHSB(RGBtoHSB(Object.assign({}, HSBtoRGB(hsbValue), { [key]: value }))); updateValue(result); updateUI(); }); item.appendChild(label); item.appendChild(colorInput); action.appendChild(item); return colorInput; }); content.appendChild(colorHue); content.appendChild(colorSelector); content.appendChild(colorAlpha); root.appendChild(content); root.appendChild(action); let colorDragging = false; let hueDragging = false; let alphaDragging = false; function updateInput() { const hex = HSBtoHEX(hsbValue); for (const [i, input] of [colorRInput, colorGInput, colorBInput].entries()) { input.value = String(Number.parseInt(hex[i * 2] + hex[i * 2 + 1], 16)); } colorAInput.value = String((hsbValue.a * 100).toFixed(0)); } function updateColorHandle() { Object.assign(colorHandle.style, { left: `${Math.floor((contentWidth * hsbValue.s) / 100)}px`, top: `${Math.floor((contentHeight * (100 - hsbValue.b)) / 100)}px`, }); } function updateColorSelector() { colorSelector.style.backgroundColor = `#${RGBtoHEX(HSBtoRGB({ h: hsbValue.h, s: 100, b: 100, a: 1, }))}`; } function updateHue() { colorHueHandle.style.top = `${Math.floor(contentHeight - (contentHeight * hsbValue.h) / 360)}px`; } function updateAlphaHandle() { alphaHandle.style.left = `${hsbValue.a * 100}%`; } function updateAlphaBg() { const { r, g, b } = HSBtoRGB(hsbValue); alphaBg.style.background = `linear-gradient(to right, rgba(${r}, ${g}, ${b}, 0) 0%, rgba(${r}, ${g}, ${b}, 1) 100%)`; } function updateUI() { updateColorHandle(); updateColorSelector(); updateHue(); updateAlphaHandle(); updateAlphaBg(); updateInput(); } function updateValue(value) { hsbValue = validateHSB(Object.assign({}, hsbValue, value)); updateInput(); if (options.onChange) { options.onChange(`#${HSBtoHEX(hsbValue)}`); } } function pickColor(event) { const rect = colorSelector.getBoundingClientRect(); const top = rect.top + (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0); const left = rect.left + document.body.scrollLeft; const saturation = Math.floor((100 * Math.max(0, Math.min(contentWidth, event.pageX - left))) / contentWidth); const brightness = Math.floor((100 * (contentHeight - Math.max(0, Math.min(contentHeight, event.pageY - top)))) / contentHeight); updateValue({ s: saturation, b: brightness, }); updateUI(); } function pickHue(event) { const top = colorHue.getBoundingClientRect().top + (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0); updateValue({ h: Math.floor((360 * (contentHeight - Math.max(0, Math.min(contentHeight, event.pageY - top)))) / contentHeight), }); updateUI(); } function pickAlpha(event) { const { pageX } = event; const rect = colorAlpha.getBoundingClientRect(); let left = pageX - rect.left; left = Math.max(handleSizeSec / 2, left); left = Math.min(left, rect.width - handleSizeSec / 2); updateValue({ a: Math.round(((left - 10 / 2) / (rect.width - 10)) * 100) / 100, }); updateUI(); } function onDrag(event) { if (colorDragging) { event.preventDefault(); pickColor(event); } if (hueDragging) { event.preventDefault(); pickHue(event); } if (alphaDragging) { event.preventDefault(); pickAlpha(event); } } function onColorSelectorDragEnd() { document.removeEventListener('mousemove', onDrag); document.removeEventListener('mouseup', onColorSelectorDragEnd); colorDragging = false; } function onColorSelectorMousedown(e) { document.addEventListener('mousemove', onDrag); document.addEventListener('mouseup', onColorSelectorDragEnd); colorDragging = true; pickColor(e); } colorSelector.addEventListener('mousedown', onColorSelectorMousedown); function onColorHueDragEnd() { document.removeEventListener('mousemove', onDrag); document.removeEventListener('mouseup', onColorHueDragEnd); hueDragging = false; } function onColorHueMousedown(event) { document.addEventListener('mousemove', onDrag); document.addEventListener('mouseup', onColorHueDragEnd); hueDragging = true; pickHue(event); } colorHue.addEventListener('mousedown', onColorHueMousedown); function onColorAlphaDragEnd() { document.removeEventListener('mousemove', onDrag); document.removeEventListener('mouseup', onColorAlphaDragEnd); alphaDragging = false; } function onColorAlphaMousedown(event) { document.addEventListener('mousemove', onDrag); document.addEventListener('mouseup', onColorAlphaDragEnd); alphaDragging = true; pickAlpha(event); } colorAlpha.addEventListener('mousedown', onColorAlphaMousedown); updateUI(); return root; };