UNPKG

shineout

Version:

Shein 前端组件库

430 lines (356 loc) 10.7 kB
import { isOne, isPercent } from './is' type RGB = string | number type HSL = string | number const CSS_INTEGER = '[-\\+]?\\d+%?' // <http://www.w3.org/TR/css3-values/#number-value> const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?' const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})` const PERMISSIVE_MATCH3 = `[\\s|\\(]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})\\s*\\)?` const PERMISSIVE_MATCH4 = `[\\s|\\(]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})\\s*\\)?` const { floor } = Math // all color RegExp const MATCH = { CSS_UNIT: new RegExp(CSS_UNIT), rgb: new RegExp(`rgb${PERMISSIVE_MATCH3}`), rgba: new RegExp(`rgba${PERMISSIVE_MATCH4}`), hsl: new RegExp(`hsl${PERMISSIVE_MATCH3}`), hsla: new RegExp(`hsla${PERMISSIVE_MATCH4}`), hsv: new RegExp(`hsv${PERMISSIVE_MATCH3}`), hsva: new RegExp(`hsva${PERMISSIVE_MATCH4}`), hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/, hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/, } function parseIntFromHex(val: string) { return parseInt(val, 16) } function convertHexToDecimal(h: string) { return parseIntFromHex(h) / 255 } // string to rgba {} const parse = (color: string) => { color = color.toLowerCase() let match if ((match = MATCH.rgb.exec(color))) { return { r: match[1], g: match[2], b: match[3] } } if ((match = MATCH.rgba.exec(color))) { return { r: match[1], g: match[2], b: match[3], a: match[4] } } if ((match = MATCH.hex8.exec(color))) { return { r: parseIntFromHex(match[1]), g: parseIntFromHex(match[2]), b: parseIntFromHex(match[3]), a: convertHexToDecimal(match[4]), } } if ((match = MATCH.hex6.exec(color))) { return { r: parseIntFromHex(match[1]), g: parseIntFromHex(match[2]), b: parseIntFromHex(match[3]), } } if ((match = MATCH.hex4.exec(color))) { return { r: parseIntFromHex(`${match[1]}${match[1]}`), g: parseIntFromHex(`${match[2]}${match[2]}`), b: parseIntFromHex(`${match[3]}${match[3]}`), a: convertHexToDecimal(`${match[4]}${match[4]}`), } } if ((match = MATCH.hex3.exec(color))) { return { r: parseIntFromHex(`${match[1]}${match[1]}`), g: parseIntFromHex(`${match[2]}${match[2]}`), b: parseIntFromHex(`${match[3]}${match[3]}`), } } return false } const toRGB = (input: string) => { if (!input || typeof input !== 'string') return '' const color = parse(input) if (!color) return '' return color.a ? `rgba(${color.r},${color.g},${color.b},${color.a})` : `rgb(${color.r},${color.g},${color.b})` } const isString = (string: string) => { if (!string) { console.error(new Error('the color is empty')) return false } if (typeof string !== 'string') { console.error(new Error(`the color is get a ${typeof string}, expect string`)) return false } return true } const dealPointZero = (string: number) => { const num = string.toFixed(1) const reg = /\.0*$/ if (reg.test(num)) return floor(Number(num)) return num } /** * parse Hex to int * @param {*} value Hex number */ const parseHex = (value: string) => parseInt(value, 16) /** * format the hex array * @param {Array} array hex array */ const formatHexArray = (array: string[], length: number) => { if (length === 6) return [`${array[1]}`, `${array[2]}`, `${array[3]}`] if (length === 3) return [`${array[1]}${array[1]}`, `${array[2]}${array[2]}`, `${array[3]}${array[3]}`] if (length === 8) return [`${array[1]}`, `${array[2]}`, `${array[3]}`, `${array[4]}`] return [`${array[1]}${array[1]}`, `${array[2]}${array[2]}`, `${array[3]}${array[3]}`, `${array[4]}${array[4]}`] } const getRgb = (arr: string[], length: number) => { const array = formatHexArray(arr, length) return `rgb(${parseHex(array[0])}, ${parseHex(array[1])}, ${parseHex(array[2])})` } const getRgba = (arr: string[], length: number) => { const array = formatHexArray(arr, length) return `rgba(${parseHex(array[0])}, ${parseHex(array[1])}, ${parseHex(array[2])}, ${dealPointZero( parseHex(array[3]) / 255 )})` } const toBound01 = (val: number | string, max: number) => { if (isOne(val)) { val = '100%' } const processPercent = isPercent(val) val = Math.min(max, Math.max(0, parseInt(String(val), 10))) // Automatically convert percentage into number if (processPercent) { val = parseInt(String(val * max), 10) / 100 } // Handle floating point rounding errors if (Math.abs(val - max) < 0.000001) { return 1 } // Convert into [0, 1] range if it isn't already return (val % max) / parseInt(String(max), 10) } const hueToRgb = (p: number, q: number, t: number) => { 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 translateHsl = (matchs: RegExpExecArray, a?: string | number) => { let [, h, s, l]: HSL[] = matchs let r: RGB let g: RGB let b: RGB h = toBound01(h, 360) s = toBound01(s, 100) l = toBound01(l, 100) if (s === 0) { r = l g = l b = l } else { const q = l < 0.5 ? l * (1 + s) : l + s - l * s const p = 2 * l - q r = hueToRgb(p, q, h + 1 / 3) g = hueToRgb(p, q, h) b = hueToRgb(p, q, h - 1 / 3) } r = floor(r * 255) g = floor(g * 255) b = floor(b * 255) return a ? `rgba(${r}, ${g}, ${b}, ${a})` : `rgb(${r}, ${g}, ${b})` } const isDarkRgb = (color: string) => { const matchs = MATCH.rgb.exec(color) || MATCH.rgba.exec(color) if (matchs) { const [, r, g, b]: RGB[] = matchs return Number(r) * 0.299 + Number(g) * 0.578 + Number(b) * 0.114 < 192 } console.error(new Error(`the string '${color}' is not a legal color`)) return undefined } const toHex = (rgb: number[], noAlpha: boolean, a: number) => { let [, r, g, b]: RGB[] = rgb ;[, r, g, b] = rgb let o const calAlhpa = !noAlpha && a r = floor(r).toString(16) g = floor(g).toString(16) b = floor(b).toString(16) if (r.length !== 2) r = `0${r}` if (g.length !== 2) g = `0${g}` if (b.length !== 2) b = `0${b}` if (calAlhpa) o = floor(a * 255).toString(16) return calAlhpa ? `#${r}${g}${b}${o}` : `#${r}${g}${b}` } // third parameter, to keep same with toHex const toHsl = (rgb: number[], _: any, a: string) => { let [, r, g, b] = rgb r = toBound01(r, 255) g = toBound01(g, 255) b = toBound01(b, 255) const max = Math.max(r, g, b) const min = Math.min(r, g, b) let h: number let s: number let l = (max + min) / 2 if (max === min) { h = 0 s = 0 // achromatic } else { const 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 default: h = 0 break } h /= 6 } h = floor(h * 360) s = floor(s * 100) l = floor(l * 100) return a ? `hsla(${h}, ${s}, ${l}, ${a})` : `hsl(${h}, ${s}, ${l})` } const rgbTranlate = (target: (rgb: (string | number)[], noAlpha?: boolean, a?: number | string) => string) => ( rgb: string, noAlpha?: boolean ) => { if (!isString(rgb)) return '' let matchs matchs = MATCH.rgb.exec(rgb) if (matchs) { return target(matchs, noAlpha) } matchs = MATCH.rgba.exec(rgb) if (matchs) { return target(matchs, noAlpha, Number(matchs[4])) } console.error(new Error(`the string '${rgb}' is not a rgb color`)) return '' } export function hexToRgb(hex: string) { if (!isString(hex)) return '' let matchs: RegExpExecArray | null matchs = MATCH.hex3.exec(hex) if (matchs) { return getRgb(matchs, 3) } matchs = MATCH.hex6.exec(hex) if (matchs) { return getRgb(matchs, 6) } matchs = MATCH.hex4.exec(hex) if (matchs) { return getRgba(matchs, 4) } matchs = MATCH.hex8.exec(hex) if (matchs) { return getRgba(matchs, 8) } console.error(new Error(`the string '${hex}' is not a hex color`)) return '' } export function hslToRgb(hsl: string) { if (!isString(hsl)) return '' let matchs: RegExpExecArray | null matchs = MATCH.hsl.exec(hsl) if (matchs) { return translateHsl(matchs) } matchs = MATCH.hsla.exec(hsl) if (matchs) { return translateHsl(matchs, matchs[4]) } console.error(new Error(`the string '${hsl}' is not a hsl color`)) return '' } export const rgbToHex = rgbTranlate(toHex) export const rgbTohsl = rgbTranlate(toHsl) export function hexToHsl(hex: string) { const temp = hexToRgb(hex) if (!temp) return '' return rgbTohsl(temp) } export function hslToHex(hsl: string, noAlpha?: boolean) { const temp = hslToRgb(hsl) if (!temp) return '' return rgbToHex(temp, noAlpha) } // dark or light export function judgeDark(color: string) { if (!isString(color)) return undefined let rgbString = color if (MATCH.hsl.test(color) || MATCH.hsla.test(color)) { rgbString = hslToRgb(color) } if (MATCH.hex3.test(color) || MATCH.hex4.test(color) || MATCH.hex6.test(color) || MATCH.hex8.test(color)) { rgbString = hexToRgb(color) } return isDarkRgb(rgbString) } export function isDark(color: string) { const result = judgeDark(color) if (result === undefined) return false return result } export function isLight(color: string) { const result = judgeDark(color) if (result === undefined) return false return !result } /** * get hsla h s l a * @param color hsl */ function getHSLA(color: string) { const hslReg = new RegExp(/hsla?\((\d{1,3}), (\d{1,3}), (\d{1,3})(, (\d{1,3}))?\)$/) hslReg.test(color) const h = RegExp.$1 const s = RegExp.$2 const l = RegExp.$3 const a = RegExp.$5.length ? RegExp.$5 : 1 return { h, s, l: parseInt(l, 10), a } } /** * darken color * @param color format rgb | rgba * @param value -100 ~ 100 */ export function darken(color: string, value: string | number) { if (!color) return '' if (!value) value = 0 value = parseInt(String(value), 10) color = toRGB(color) const hsl = rgbTohsl(color) const { h, s, l, a } = getHSLA(hsl) return hslToRgb(`hsla(${h},${s}%,${l - value}%,${a})`) } /** * fade color * @param color format rgb * @param alpha 0-1 */ export function fade(color: string, alpha = 1) { if (!color) return '' color = toRGB(color) const hsl = rgbTohsl(color) const { h, s, l } = getHSLA(hsl) return hslToRgb(`hsla(${h},${s}%,${l}%,${alpha})`) }