UNPKG

element-plus

Version:

A Component Library for Vue3.0

334 lines (284 loc) 9.27 kB
const hsv2hsl = function(hue: number, sat: number, val: number) { return [ hue, (sat * val / ((hue = (2 - sat) * val) < 1 ? hue : 2 - hue)) || 0, hue / 2, ] } // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1 // <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0> const isOnePointZero = function(n: unknown) { return typeof n === 'string' && n.indexOf('.') !== -1 && parseFloat(n) === 1 } const isPercentage = function(n: unknown) { return typeof n === 'string' && n.indexOf('%') !== -1 } // Take input from [0, n] and return it as [0, 1] const bound01 = function(value: number | string, max: number | string) { if (isOnePointZero(value)) value = '100%' const processPercent = isPercentage(value) value = Math.min((max as number), Math.max(0, parseFloat(value + ''))) // Automatically convert percentage into number if (processPercent) { value = parseInt((value * (max as number)) + '', 10) / 100 } // Handle floating point rounding errors if ((Math.abs(value - (max as number)) < 0.000001)) { return 1 } // Convert into [0, 1] range if it isn't already return (value % (max as number)) / parseFloat(max as string) } const INT_HEX_MAP = { 10: 'A', 11: 'B', 12: 'C', 13: 'D', 14: 'E', 15: 'F' } const toHex = function({ r, g, b }) { const hexOne = function(value: number) { value = Math.min(Math.round(value), 255) const high = Math.floor(value / 16) const low = value % 16 return '' + (INT_HEX_MAP[high] || high) + (INT_HEX_MAP[low] || low) } if (isNaN(r) || isNaN(g) || isNaN(b)) return '' return '#' + hexOne(r) + hexOne(g) + hexOne(b) } const HEX_INT_MAP = { A: 10, B: 11, C: 12, D: 13, E: 14, F: 15 } const parseHexChannel = function(hex: string) { if (hex.length === 2) { return (HEX_INT_MAP[hex[0].toUpperCase()] || +hex[0]) * 16 + (HEX_INT_MAP[hex[1].toUpperCase()] || +hex[1]) } return HEX_INT_MAP[hex[1].toUpperCase()] || +hex[1] } const hsl2hsv = function(hue: number, sat: number, light: number) { sat = sat / 100 light = light / 100 let smin = sat const lmin = Math.max(light, 0.01) // let sv // let v light *= 2 sat *= (light <= 1) ? light : 2 - light smin *= lmin <= 1 ? lmin : 2 - lmin const v = (light + sat) / 2 const sv = light === 0 ? (2 * smin) / (lmin + smin) : (2 * sat) / (light + sat) return { h: hue, s: sv * 100, v: v * 100, } } // `rgbToHsv` // Converts an RGB color value to HSV // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1] // *Returns:* { h, s, v } in [0,1] const rgb2hsv = function(r, g, b) { r = bound01(r, 255) g = bound01(g, 255) b = bound01(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, v: v * 100 } } // `hsvToRgb` // Converts an HSV color value to RGB. // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100] // *Returns:* { r, g, b } in the set [0, 255] const hsv2rgb = function(h, s, v) { h = bound01(h, 360) * 6 s = bound01(s, 100) v = bound01(v, 100) const i = Math.floor(h) const f = h - i const p = v * (1 - s) const q = v * (1 - f * s) const t = v * (1 - (1 - f) * s) const mod = i % 6 const r = [v, q, p, p, t, v][mod] const g = [t, v, v, q, p, p][mod] const b = [p, p, t, v, v, q][mod] return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255), } } export interface Options { enableAlpha: boolean format: string value?: string } export default class Color { private _hue = 0 private _saturation = 100 private _value = 100 private _alpha = 100 public enableAlpha = false public format = 'hex' public value = '' public selected?: boolean constructor(options?: Options) { options = options || {} as Options for (const option in options) { if (options.hasOwnProperty(option)) { this[option] = options[option] } } this.doOnChange() } set(prop: {[key: string]: any;} | any, value?: number) { if (arguments.length === 1 && typeof prop === 'object') { for (const p in prop) { if (prop.hasOwnProperty(p)) { this.set(p, prop[p]) } } return } this['_' + prop] = value this.doOnChange() } get(prop: string) { return this['_' + prop] } toRgb() { return hsv2rgb(this._hue, this._saturation, this._value) } fromString(value) { if (!value) { this._hue = 0 this._saturation = 100 this._value = 100 this.doOnChange() return } const fromHSV = (h, s, v) => { this._hue = Math.max(0, Math.min(360, h)) this._saturation = Math.max(0, Math.min(100, s)) this._value = Math.max(0, Math.min(100, v)) this.doOnChange() } if (value.indexOf('hsl') !== -1) { const parts = value.replace(/hsla|hsl|\(|\)/gm, '') .split(/\s|,/g).filter(val => val !== '').map((val, index) => index > 2 ? parseFloat(val) : parseInt(val, 10)) if (parts.length === 4) { this._alpha = Math.floor(parseFloat(parts[3]) * 100) } else if (parts.length === 3) { this._alpha = 100 } if (parts.length >= 3) { const { h, s, v } = hsl2hsv(parts[0], parts[1], parts[2]) fromHSV(h, s, v) } } else if (value.indexOf('hsv') !== -1) { const parts = value.replace(/hsva|hsv|\(|\)/gm, '') .split(/\s|,/g).filter(val => val !== '').map((val, index) => index > 2 ? parseFloat(val) : parseInt(val, 10)) if (parts.length === 4) { this._alpha = Math.floor(parseFloat(parts[3]) * 100) } else if (parts.length === 3) { this._alpha = 100 } if (parts.length >= 3) { fromHSV(parts[0], parts[1], parts[2]) } } else if (value.indexOf('rgb') !== -1) { const parts = value.replace(/rgba|rgb|\(|\)/gm, '') .split(/\s|,/g).filter(val => val !== '').map((val, index) => index > 2 ? parseFloat(val) : parseInt(val, 10)) if (parts.length === 4) { this._alpha = Math.floor(parseFloat(parts[3]) * 100) } else if (parts.length === 3) { this._alpha = 100 } if (parts.length >= 3) { const { h, s, v } = rgb2hsv(parts[0], parts[1], parts[2]) fromHSV(h, s, v) } } else if (value.indexOf('#') !== -1) { const hex = value.replace('#', '').trim() if (!/^[0-9a-fA-F]{3}$|^[0-9a-fA-F]{6}$|^[0-9a-fA-F]{8}$/.test(hex)) return let r, g, b if (hex.length === 3) { r = parseHexChannel(hex[0] + hex[0]) g = parseHexChannel(hex[1] + hex[1]) b = parseHexChannel(hex[2] + hex[2]) } else if (hex.length === 6 || hex.length === 8) { r = parseHexChannel(hex.substring(0, 2)) g = parseHexChannel(hex.substring(2, 4)) b = parseHexChannel(hex.substring(4, 6)) } if (hex.length === 8) { this._alpha = Math.floor(parseHexChannel(hex.substring(6)) / 255 * 100) } else if (hex.length === 3 || hex.length === 6) { this._alpha = 100 } const { h, s, v } = rgb2hsv(r, g, b) fromHSV(h, s, v) } } compare(color) { return Math.abs(color._hue - this._hue) < 2 && Math.abs(color._saturation - this._saturation) < 1 && Math.abs(color._value - this._value) < 1 && Math.abs(color._alpha - this._alpha) < 1 } doOnChange() { const { _hue, _saturation, _value, _alpha, format } = this if (this.enableAlpha) { switch (format) { case 'hsl': { const hsl = hsv2hsl(_hue, _saturation / 100, _value / 100) this.value = `hsla(${ _hue }, ${ Math.round(hsl[1] * 100) }%, ${ Math.round(hsl[2] * 100) }%, ${ _alpha / 100})` break } case 'hsv': { this.value = `hsva(${ _hue }, ${ Math.round(_saturation) }%, ${ Math.round(_value) }%, ${ _alpha / 100})` break } default: { const { r, g, b } = hsv2rgb(_hue, _saturation, _value) this.value = `rgba(${r}, ${g}, ${b}, ${ _alpha / 100 })` } } } else { switch (format) { case 'hsl': { const hsl = hsv2hsl(_hue, _saturation / 100, _value / 100) this.value = `hsl(${ _hue }, ${ Math.round(hsl[1] * 100) }%, ${ Math.round(hsl[2] * 100) }%)` break } case 'hsv': { this.value = `hsv(${ _hue }, ${ Math.round(_saturation) }%, ${ Math.round(_value) }%)` break } case 'rgb':{ const { r, g, b } = hsv2rgb(_hue, _saturation, _value) this.value = `rgb(${r}, ${g}, ${b})` break } default: { this.value = toHex(hsv2rgb(_hue, _saturation, _value)) } } } } }