first commit

This commit is contained in:
lxj
2025-06-26 12:38:35 +08:00
commit c536475e19
1236 changed files with 186274 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
# 1.0.0 - 2016-01-07
- Removed: unused speed test
- Added: Automatic routing between previously unsupported conversions
([#27](https://github.com/Qix-/color-convert/pull/27))
- Removed: `xxx2xxx()` and `xxx2xxxRaw()` functions
([#27](https://github.com/Qix-/color-convert/pull/27))
- Removed: `convert()` class
([#27](https://github.com/Qix-/color-convert/pull/27))
- Changed: all functions to lookup dictionary
([#27](https://github.com/Qix-/color-convert/pull/27))
- Changed: `ansi` to `ansi256`
([#27](https://github.com/Qix-/color-convert/pull/27))
- Fixed: argument grouping for functions requiring only one argument
([#27](https://github.com/Qix-/color-convert/pull/27))
# 0.6.0 - 2015-07-23
- Added: methods to handle
[ANSI](https://en.wikipedia.org/wiki/ANSI_escape_code#Colors) 16/256 colors:
- rgb2ansi16
- rgb2ansi
- hsl2ansi16
- hsl2ansi
- hsv2ansi16
- hsv2ansi
- hwb2ansi16
- hwb2ansi
- cmyk2ansi16
- cmyk2ansi
- keyword2ansi16
- keyword2ansi
- ansi162rgb
- ansi162hsl
- ansi162hsv
- ansi162hwb
- ansi162cmyk
- ansi162keyword
- ansi2rgb
- ansi2hsl
- ansi2hsv
- ansi2hwb
- ansi2cmyk
- ansi2keyword
([#18](https://github.com/harthur/color-convert/pull/18))
# 0.5.3 - 2015-06-02
- Fixed: hsl2hsv does not return `NaN` anymore when using `[0,0,0]`
([#15](https://github.com/harthur/color-convert/issues/15))
---
Check out commit logs for older releases

View File

@@ -0,0 +1,21 @@
Copyright (c) 2011-2016 Heather Arthur <fayearthur@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,68 @@
# color-convert
[![Build Status](https://travis-ci.org/Qix-/color-convert.svg?branch=master)](https://travis-ci.org/Qix-/color-convert)
Color-convert is a color conversion library for JavaScript and node.
It converts all ways between `rgb`, `hsl`, `hsv`, `hwb`, `cmyk`, `ansi`, `ansi16`, `hex` strings, and CSS `keyword`s (will round to closest):
```js
var convert = require('color-convert');
convert.rgb.hsl(140, 200, 100); // [96, 48, 59]
convert.keyword.rgb('blue'); // [0, 0, 255]
var rgbChannels = convert.rgb.channels; // 3
var cmykChannels = convert.cmyk.channels; // 4
var ansiChannels = convert.ansi16.channels; // 1
```
# Install
```console
$ npm install color-convert
```
# API
Simply get the property of the _from_ and _to_ conversion that you're looking for.
All functions have a rounded and unrounded variant. By default, return values are rounded. To get the unrounded (raw) results, simply tack on `.raw` to the function.
All 'from' functions have a hidden property called `.channels` that indicates the number of channels the function expects (not including alpha).
```js
var convert = require('color-convert');
// Hex to LAB
convert.hex.lab('DEADBF'); // [ 76, 21, -2 ]
convert.hex.lab.raw('DEADBF'); // [ 75.56213190997677, 20.653827952644754, -2.290532499330533 ]
// RGB to CMYK
convert.rgb.cmyk(167, 255, 4); // [ 35, 0, 98, 0 ]
convert.rgb.cmyk.raw(167, 255, 4); // [ 34.509803921568626, 0, 98.43137254901961, 0 ]
```
### Arrays
All functions that accept multiple arguments also support passing an array.
Note that this does **not** apply to functions that convert from a color that only requires one value (e.g. `keyword`, `ansi256`, `hex`, etc.)
```js
var convert = require('color-convert');
convert.rgb.hex(123, 45, 67); // '7B2D43'
convert.rgb.hex([123, 45, 67]); // '7B2D43'
```
## Routing
Conversions that don't have an _explicitly_ defined conversion (in [conversions.js](conversions.js)), but can be converted by means of sub-conversions (e.g. XYZ -> **RGB** -> CMYK), are automatically routed together. This allows just about any color model supported by `color-convert` to be converted to any other model, so long as a sub-conversion path exists. This is also true for conversions requiring more than one step in between (e.g. LCH -> **LAB** -> **XYZ** -> **RGB** -> Hex).
Keep in mind that extensive conversions _may_ result in a loss of precision, and exist only to be complete. For a list of "direct" (single-step) conversions, see [conversions.js](conversions.js).
# Contribute
If there is a new model you would like to support, or want to add a direct conversion between two existing models, please send us a pull request.
# License
Copyright &copy; 2011-2016, Heather Arthur and Josh Junon. Licensed under the [MIT License](LICENSE).

View File

@@ -0,0 +1,839 @@
/* MIT license */
/* eslint-disable no-mixed-operators */
import cssKeywords from '../color-name';
// NOTE: conversions should only return primitive values (i.e. arrays, or
// values that give correct `typeof` results).
// do not use box values types (i.e. Number(), String(), etc.)
const reverseKeywords = {};
for (const key of Object.keys(cssKeywords)) {
reverseKeywords[cssKeywords[key]] = key;
}
const convert = {
rgb: {channels: 3, labels: 'rgb'},
hsl: {channels: 3, labels: 'hsl'},
hsv: {channels: 3, labels: 'hsv'},
hwb: {channels: 3, labels: 'hwb'},
cmyk: {channels: 4, labels: 'cmyk'},
xyz: {channels: 3, labels: 'xyz'},
lab: {channels: 3, labels: 'lab'},
lch: {channels: 3, labels: 'lch'},
hex: {channels: 1, labels: ['hex']},
keyword: {channels: 1, labels: ['keyword']},
ansi16: {channels: 1, labels: ['ansi16']},
ansi256: {channels: 1, labels: ['ansi256']},
hcg: {channels: 3, labels: ['h', 'c', 'g']},
apple: {channels: 3, labels: ['r16', 'g16', 'b16']},
gray: {channels: 1, labels: ['gray']}
};
export default convert;
// Hide .channels and .labels properties
for (const model of Object.keys(convert)) {
if (!('channels' in convert[model])) {
throw new Error('missing channels property: ' + model);
}
if (!('labels' in convert[model])) {
throw new Error('missing channel labels property: ' + model);
}
if (convert[model].labels.length !== convert[model].channels) {
throw new Error('channel and label counts mismatch: ' + model);
}
const {channels, labels} = convert[model];
delete convert[model].channels;
delete convert[model].labels;
Object.defineProperty(convert[model], 'channels', {value: channels});
Object.defineProperty(convert[model], 'labels', {value: labels});
}
convert.rgb.hsl = function (rgb) {
const r = rgb[0] / 255;
const g = rgb[1] / 255;
const b = rgb[2] / 255;
const min = Math.min(r, g, b);
const max = Math.max(r, g, b);
const delta = max - min;
let h;
let s;
if (max === min) {
h = 0;
} else if (r === max) {
h = (g - b) / delta;
} else if (g === max) {
h = 2 + (b - r) / delta;
} else if (b === max) {
h = 4 + (r - g) / delta;
}
h = Math.min(h * 60, 360);
if (h < 0) {
h += 360;
}
const l = (min + max) / 2;
if (max === min) {
s = 0;
} else if (l <= 0.5) {
s = delta / (max + min);
} else {
s = delta / (2 - max - min);
}
return [h, s * 100, l * 100];
};
convert.rgb.hsv = function (rgb) {
let rdif;
let gdif;
let bdif;
let h;
let s;
const r = rgb[0] / 255;
const g = rgb[1] / 255;
const b = rgb[2] / 255;
const v = Math.max(r, g, b);
const diff = v - Math.min(r, g, b);
const diffc = function (c) {
return (v - c) / 6 / diff + 1 / 2;
};
if (diff === 0) {
h = 0;
s = 0;
} else {
s = diff / v;
rdif = diffc(r);
gdif = diffc(g);
bdif = diffc(b);
if (r === v) {
h = bdif - gdif;
} else if (g === v) {
h = (1 / 3) + rdif - bdif;
} else if (b === v) {
h = (2 / 3) + gdif - rdif;
}
if (h < 0) {
h += 1;
} else if (h > 1) {
h -= 1;
}
}
return [
h * 360,
s * 100,
v * 100
];
};
convert.rgb.hwb = function (rgb) {
const r = rgb[0];
const g = rgb[1];
let b = rgb[2];
const h = convert.rgb.hsl(rgb)[0];
const w = 1 / 255 * Math.min(r, Math.min(g, b));
b = 1 - 1 / 255 * Math.max(r, Math.max(g, b));
return [h, w * 100, b * 100];
};
convert.rgb.cmyk = function (rgb) {
const r = rgb[0] / 255;
const g = rgb[1] / 255;
const b = rgb[2] / 255;
const k = Math.min(1 - r, 1 - g, 1 - b);
const c = (1 - r - k) / (1 - k) || 0;
const m = (1 - g - k) / (1 - k) || 0;
const y = (1 - b - k) / (1 - k) || 0;
return [c * 100, m * 100, y * 100, k * 100];
};
function comparativeDistance(x, y) {
/*
See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance
*/
return (
((x[0] - y[0]) ** 2) +
((x[1] - y[1]) ** 2) +
((x[2] - y[2]) ** 2)
);
}
convert.rgb.keyword = function (rgb) {
const reversed = reverseKeywords[rgb];
if (reversed) {
return reversed;
}
let currentClosestDistance = Infinity;
let currentClosestKeyword;
for (const keyword of Object.keys(cssKeywords)) {
const value = cssKeywords[keyword];
// Compute comparative distance
const distance = comparativeDistance(rgb, value);
// Check if its less, if so set as closest
if (distance < currentClosestDistance) {
currentClosestDistance = distance;
currentClosestKeyword = keyword;
}
}
return currentClosestKeyword;
};
convert.keyword.rgb = function (keyword) {
return cssKeywords[keyword];
};
convert.rgb.xyz = function (rgb) {
let r = rgb[0] / 255;
let g = rgb[1] / 255;
let b = rgb[2] / 255;
// Assume sRGB
r = r > 0.04045 ? (((r + 0.055) / 1.055) ** 2.4) : (r / 12.92);
g = g > 0.04045 ? (((g + 0.055) / 1.055) ** 2.4) : (g / 12.92);
b = b > 0.04045 ? (((b + 0.055) / 1.055) ** 2.4) : (b / 12.92);
const x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
const y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
const z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
return [x * 100, y * 100, z * 100];
};
convert.rgb.lab = function (rgb) {
const xyz = convert.rgb.xyz(rgb);
let x = xyz[0];
let y = xyz[1];
let z = xyz[2];
x /= 95.047;
y /= 100;
z /= 108.883;
x = x > 0.008856 ? (x ** (1 / 3)) : (7.787 * x) + (16 / 116);
y = y > 0.008856 ? (y ** (1 / 3)) : (7.787 * y) + (16 / 116);
z = z > 0.008856 ? (z ** (1 / 3)) : (7.787 * z) + (16 / 116);
const l = (116 * y) - 16;
const a = 500 * (x - y);
const b = 200 * (y - z);
return [l, a, b];
};
convert.hsl.rgb = function (hsl) {
const h = hsl[0] / 360;
const s = hsl[1] / 100;
const l = hsl[2] / 100;
let t2;
let t3;
let val;
if (s === 0) {
val = l * 255;
return [val, val, val];
}
if (l < 0.5) {
t2 = l * (1 + s);
} else {
t2 = l + s - l * s;
}
const t1 = 2 * l - t2;
const rgb = [0, 0, 0];
for (let i = 0; i < 3; i++) {
t3 = h + 1 / 3 * -(i - 1);
if (t3 < 0) {
t3++;
}
if (t3 > 1) {
t3--;
}
if (6 * t3 < 1) {
val = t1 + (t2 - t1) * 6 * t3;
} else if (2 * t3 < 1) {
val = t2;
} else if (3 * t3 < 2) {
val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
} else {
val = t1;
}
rgb[i] = val * 255;
}
return rgb;
};
convert.hsl.hsv = function (hsl) {
const h = hsl[0];
let s = hsl[1] / 100;
let l = hsl[2] / 100;
let smin = s;
const lmin = Math.max(l, 0.01);
l *= 2;
s *= (l <= 1) ? l : 2 - l;
smin *= lmin <= 1 ? lmin : 2 - lmin;
const v = (l + s) / 2;
const sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s);
return [h, sv * 100, v * 100];
};
convert.hsv.rgb = function (hsv) {
const h = hsv[0] / 60;
const s = hsv[1] / 100;
let v = hsv[2] / 100;
const hi = Math.floor(h) % 6;
const f = h - Math.floor(h);
const p = 255 * v * (1 - s);
const q = 255 * v * (1 - (s * f));
const t = 255 * v * (1 - (s * (1 - f)));
v *= 255;
switch (hi) {
case 0:
return [v, t, p];
case 1:
return [q, v, p];
case 2:
return [p, v, t];
case 3:
return [p, q, v];
case 4:
return [t, p, v];
case 5:
return [v, p, q];
}
};
convert.hsv.hsl = function (hsv) {
const h = hsv[0];
const s = hsv[1] / 100;
const v = hsv[2] / 100;
const vmin = Math.max(v, 0.01);
let sl;
let l;
l = (2 - s) * v;
const lmin = (2 - s) * vmin;
sl = s * vmin;
sl /= (lmin <= 1) ? lmin : 2 - lmin;
sl = sl || 0;
l /= 2;
return [h, sl * 100, l * 100];
};
// http://dev.w3.org/csswg/css-color/#hwb-to-rgb
convert.hwb.rgb = function (hwb) {
const h = hwb[0] / 360;
let wh = hwb[1] / 100;
let bl = hwb[2] / 100;
const ratio = wh + bl;
let f;
// Wh + bl cant be > 1
if (ratio > 1) {
wh /= ratio;
bl /= ratio;
}
const i = Math.floor(6 * h);
const v = 1 - bl;
f = 6 * h - i;
if ((i & 0x01) !== 0) {
f = 1 - f;
}
const n = wh + f * (v - wh); // Linear interpolation
let r;
let g;
let b;
/* eslint-disable max-statements-per-line,no-multi-spaces */
switch (i) {
default:
case 6:
case 0: r = v; g = n; b = wh; break;
case 1: r = n; g = v; b = wh; break;
case 2: r = wh; g = v; b = n; break;
case 3: r = wh; g = n; b = v; break;
case 4: r = n; g = wh; b = v; break;
case 5: r = v; g = wh; b = n; break;
}
/* eslint-enable max-statements-per-line,no-multi-spaces */
return [r * 255, g * 255, b * 255];
};
convert.cmyk.rgb = function (cmyk) {
const c = cmyk[0] / 100;
const m = cmyk[1] / 100;
const y = cmyk[2] / 100;
const k = cmyk[3] / 100;
const r = 1 - Math.min(1, c * (1 - k) + k);
const g = 1 - Math.min(1, m * (1 - k) + k);
const b = 1 - Math.min(1, y * (1 - k) + k);
return [r * 255, g * 255, b * 255];
};
convert.xyz.rgb = function (xyz) {
const x = xyz[0] / 100;
const y = xyz[1] / 100;
const z = xyz[2] / 100;
let r;
let g;
let b;
r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
// Assume sRGB
r = r > 0.0031308
? ((1.055 * (r ** (1.0 / 2.4))) - 0.055)
: r * 12.92;
g = g > 0.0031308
? ((1.055 * (g ** (1.0 / 2.4))) - 0.055)
: g * 12.92;
b = b > 0.0031308
? ((1.055 * (b ** (1.0 / 2.4))) - 0.055)
: b * 12.92;
r = Math.min(Math.max(0, r), 1);
g = Math.min(Math.max(0, g), 1);
b = Math.min(Math.max(0, b), 1);
return [r * 255, g * 255, b * 255];
};
convert.xyz.lab = function (xyz) {
let x = xyz[0];
let y = xyz[1];
let z = xyz[2];
x /= 95.047;
y /= 100;
z /= 108.883;
x = x > 0.008856 ? (x ** (1 / 3)) : (7.787 * x) + (16 / 116);
y = y > 0.008856 ? (y ** (1 / 3)) : (7.787 * y) + (16 / 116);
z = z > 0.008856 ? (z ** (1 / 3)) : (7.787 * z) + (16 / 116);
const l = (116 * y) - 16;
const a = 500 * (x - y);
const b = 200 * (y - z);
return [l, a, b];
};
convert.lab.xyz = function (lab) {
const l = lab[0];
const a = lab[1];
const b = lab[2];
let x;
let y;
let z;
y = (l + 16) / 116;
x = a / 500 + y;
z = y - b / 200;
const y2 = y ** 3;
const x2 = x ** 3;
const z2 = z ** 3;
y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787;
x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787;
z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787;
x *= 95.047;
y *= 100;
z *= 108.883;
return [x, y, z];
};
convert.lab.lch = function (lab) {
const l = lab[0];
const a = lab[1];
const b = lab[2];
let h;
const hr = Math.atan2(b, a);
h = hr * 360 / 2 / Math.PI;
if (h < 0) {
h += 360;
}
const c = Math.sqrt(a * a + b * b);
return [l, c, h];
};
convert.lch.lab = function (lch) {
const l = lch[0];
const c = lch[1];
const h = lch[2];
const hr = h / 360 * 2 * Math.PI;
const a = c * Math.cos(hr);
const b = c * Math.sin(hr);
return [l, a, b];
};
convert.rgb.ansi16 = function (args, saturation = null) {
const [r, g, b] = args;
let value = saturation === null ? convert.rgb.hsv(args)[2] : saturation; // Hsv -> ansi16 optimization
value = Math.round(value / 50);
if (value === 0) {
return 30;
}
let ansi = 30
+ ((Math.round(b / 255) << 2)
| (Math.round(g / 255) << 1)
| Math.round(r / 255));
if (value === 2) {
ansi += 60;
}
return ansi;
};
convert.hsv.ansi16 = function (args) {
// Optimization here; we already know the value and don't need to get
// it converted for us.
return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]);
};
convert.rgb.ansi256 = function (args) {
const r = args[0];
const g = args[1];
const b = args[2];
// We use the extended greyscale palette here, with the exception of
// black and white. normal palette only has 4 greyscale shades.
if (r === g && g === b) {
if (r < 8) {
return 16;
}
if (r > 248) {
return 231;
}
return Math.round(((r - 8) / 247) * 24) + 232;
}
const ansi = 16
+ (36 * Math.round(r / 255 * 5))
+ (6 * Math.round(g / 255 * 5))
+ Math.round(b / 255 * 5);
return ansi;
};
convert.ansi16.rgb = function (args) {
let color = args % 10;
// Handle greyscale
if (color === 0 || color === 7) {
if (args > 50) {
color += 3.5;
}
color = color / 10.5 * 255;
return [color, color, color];
}
const mult = (~~(args > 50) + 1) * 0.5;
const r = ((color & 1) * mult) * 255;
const g = (((color >> 1) & 1) * mult) * 255;
const b = (((color >> 2) & 1) * mult) * 255;
return [r, g, b];
};
convert.ansi256.rgb = function (args) {
// Handle greyscale
if (args >= 232) {
const c = (args - 232) * 10 + 8;
return [c, c, c];
}
args -= 16;
let rem;
const r = Math.floor(args / 36) / 5 * 255;
const g = Math.floor((rem = args % 36) / 6) / 5 * 255;
const b = (rem % 6) / 5 * 255;
return [r, g, b];
};
convert.rgb.hex = function (args) {
const integer = ((Math.round(args[0]) & 0xFF) << 16)
+ ((Math.round(args[1]) & 0xFF) << 8)
+ (Math.round(args[2]) & 0xFF);
const string = integer.toString(16).toUpperCase();
return '000000'.substring(string.length) + string;
};
convert.hex.rgb = function (args) {
const match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);
if (!match) {
return [0, 0, 0];
}
let colorString = match[0];
if (match[0].length === 3) {
colorString = colorString.split('').map(char => {
return char + char;
}).join('');
}
const integer = parseInt(colorString, 16);
const r = (integer >> 16) & 0xFF;
const g = (integer >> 8) & 0xFF;
const b = integer & 0xFF;
return [r, g, b];
};
convert.rgb.hcg = function (rgb) {
const r = rgb[0] / 255;
const g = rgb[1] / 255;
const b = rgb[2] / 255;
const max = Math.max(Math.max(r, g), b);
const min = Math.min(Math.min(r, g), b);
const chroma = (max - min);
let grayscale;
let hue;
if (chroma < 1) {
grayscale = min / (1 - chroma);
} else {
grayscale = 0;
}
if (chroma <= 0) {
hue = 0;
} else
if (max === r) {
hue = ((g - b) / chroma) % 6;
} else
if (max === g) {
hue = 2 + (b - r) / chroma;
} else {
hue = 4 + (r - g) / chroma;
}
hue /= 6;
hue %= 1;
return [hue * 360, chroma * 100, grayscale * 100];
};
convert.hsl.hcg = function (hsl) {
const s = hsl[1] / 100;
const l = hsl[2] / 100;
const c = l < 0.5 ? (2.0 * s * l) : (2.0 * s * (1.0 - l));
let f = 0;
if (c < 1.0) {
f = (l - 0.5 * c) / (1.0 - c);
}
return [hsl[0], c * 100, f * 100];
};
convert.hsv.hcg = function (hsv) {
const s = hsv[1] / 100;
const v = hsv[2] / 100;
const c = s * v;
let f = 0;
if (c < 1.0) {
f = (v - c) / (1 - c);
}
return [hsv[0], c * 100, f * 100];
};
convert.hcg.rgb = function (hcg) {
const h = hcg[0] / 360;
const c = hcg[1] / 100;
const g = hcg[2] / 100;
if (c === 0.0) {
return [g * 255, g * 255, g * 255];
}
const pure = [0, 0, 0];
const hi = (h % 1) * 6;
const v = hi % 1;
const w = 1 - v;
let mg = 0;
/* eslint-disable max-statements-per-line */
switch (Math.floor(hi)) {
case 0:
pure[0] = 1; pure[1] = v; pure[2] = 0; break;
case 1:
pure[0] = w; pure[1] = 1; pure[2] = 0; break;
case 2:
pure[0] = 0; pure[1] = 1; pure[2] = v; break;
case 3:
pure[0] = 0; pure[1] = w; pure[2] = 1; break;
case 4:
pure[0] = v; pure[1] = 0; pure[2] = 1; break;
default:
pure[0] = 1; pure[1] = 0; pure[2] = w;
}
/* eslint-enable max-statements-per-line */
mg = (1.0 - c) * g;
return [
(c * pure[0] + mg) * 255,
(c * pure[1] + mg) * 255,
(c * pure[2] + mg) * 255
];
};
convert.hcg.hsv = function (hcg) {
const c = hcg[1] / 100;
const g = hcg[2] / 100;
const v = c + g * (1.0 - c);
let f = 0;
if (v > 0.0) {
f = c / v;
}
return [hcg[0], f * 100, v * 100];
};
convert.hcg.hsl = function (hcg) {
const c = hcg[1] / 100;
const g = hcg[2] / 100;
const l = g * (1.0 - c) + 0.5 * c;
let s = 0;
if (l > 0.0 && l < 0.5) {
s = c / (2 * l);
} else
if (l >= 0.5 && l < 1.0) {
s = c / (2 * (1 - l));
}
return [hcg[0], s * 100, l * 100];
};
convert.hcg.hwb = function (hcg) {
const c = hcg[1] / 100;
const g = hcg[2] / 100;
const v = c + g * (1.0 - c);
return [hcg[0], (v - c) * 100, (1 - v) * 100];
};
convert.hwb.hcg = function (hwb) {
const w = hwb[1] / 100;
const b = hwb[2] / 100;
const v = 1 - b;
const c = v - w;
let g = 0;
if (c < 1) {
g = (v - c) / (1 - c);
}
return [hwb[0], c * 100, g * 100];
};
convert.apple.rgb = function (apple) {
return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255];
};
convert.rgb.apple = function (rgb) {
return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535];
};
convert.gray.rgb = function (args) {
return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255];
};
convert.gray.hsl = function (args) {
return [0, 0, args[0]];
};
convert.gray.hsv = convert.gray.hsl;
convert.gray.hwb = function (gray) {
return [0, 100, gray[0]];
};
convert.gray.cmyk = function (gray) {
return [0, 0, 0, gray[0]];
};
convert.gray.lab = function (gray) {
return [gray[0], 0, 0];
};
convert.gray.hex = function (gray) {
const val = Math.round(gray[0] / 100 * 255) & 0xFF;
const integer = (val << 16) + (val << 8) + val;
const string = integer.toString(16).toUpperCase();
return '000000'.substring(string.length) + string;
};
convert.rgb.gray = function (rgb) {
const val = (rgb[0] + rgb[1] + rgb[2]) / 3;
return [val / 255 * 100];
};

View File

@@ -0,0 +1,81 @@
import route from './route'
import conversions from './conversions'
const convert = {};
const models = Object.keys(conversions);
function wrapRaw(fn) {
const wrappedFn = function (...args) {
const arg0 = args[0];
if (arg0 === undefined || arg0 === null) {
return arg0;
}
if (arg0.length > 1) {
args = arg0;
}
return fn(args);
};
// Preserve .conversion property if there is one
if ('conversion' in fn) {
wrappedFn.conversion = fn.conversion;
}
return wrappedFn;
}
function wrapRounded(fn) {
const wrappedFn = function (...args) {
const arg0 = args[0];
if (arg0 === undefined || arg0 === null) {
return arg0;
}
if (arg0.length > 1) {
args = arg0;
}
const result = fn(args);
// We're assuming the result is an array here.
// see notice in conversions.js; don't use box types
// in conversion functions.
if (typeof result === 'object') {
for (let len = result.length, i = 0; i < len; i++) {
result[i] = Math.round(result[i]);
}
}
return result;
};
// Preserve .conversion property if there is one
if ('conversion' in fn) {
wrappedFn.conversion = fn.conversion;
}
return wrappedFn;
}
models.forEach(fromModel => {
convert[fromModel] = {};
Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels});
Object.defineProperty(convert[fromModel], 'labels', {value: conversions[fromModel].labels});
const routes = route(fromModel);
const routeModels = Object.keys(routes);
routeModels.forEach(toModel => {
const fn = routes[toModel];
convert[fromModel][toModel] = wrapRounded(fn);
convert[fromModel][toModel].raw = wrapRaw(fn);
});
});
export default convert;

View File

@@ -0,0 +1,48 @@
{
"name": "color-convert",
"description": "Plain color conversion functions",
"version": "2.0.1",
"author": "Heather Arthur <fayearthur@gmail.com>",
"license": "MIT",
"repository": "Qix-/color-convert",
"scripts": {
"pretest": "xo",
"test": "node test/basic.js"
},
"engines": {
"node": ">=7.0.0"
},
"keywords": [
"color",
"colour",
"convert",
"converter",
"conversion",
"rgb",
"hsl",
"hsv",
"hwb",
"cmyk",
"ansi",
"ansi16"
],
"files": [
"index.js",
"conversions.js",
"route.js"
],
"xo": {
"rules": {
"default-case": 0,
"no-inline-comments": 0,
"operator-linebreak": 0
}
},
"devDependencies": {
"chalk": "^2.4.2",
"xo": "^0.24.0"
},
"dependencies": {
"color-name": "~1.1.4"
}
}

View File

@@ -0,0 +1,97 @@
import conversions from './conversions'
/*
This function routes a model to all other models.
all functions that are routed have a property `.conversion` attached
to the returned synthetic function. This property is an array
of strings, each with the steps in between the 'from' and 'to'
color models (inclusive).
conversions that are not possible simply are not included.
*/
function buildGraph() {
const graph = {};
// https://jsperf.com/object-keys-vs-for-in-with-closure/3
const models = Object.keys(conversions);
for (let len = models.length, i = 0; i < len; i++) {
graph[models[i]] = {
// http://jsperf.com/1-vs-infinity
// micro-opt, but this is simple.
distance: -1,
parent: null
};
}
return graph;
}
// https://en.wikipedia.org/wiki/Breadth-first_search
function deriveBFS(fromModel) {
const graph = buildGraph();
const queue = [fromModel]; // Unshift -> queue -> pop
graph[fromModel].distance = 0;
while (queue.length) {
const current = queue.pop();
const adjacents = Object.keys(conversions[current]);
for (let len = adjacents.length, i = 0; i < len; i++) {
const adjacent = adjacents[i];
const node = graph[adjacent];
if (node.distance === -1) {
node.distance = graph[current].distance + 1;
node.parent = current;
queue.unshift(adjacent);
}
}
}
return graph;
}
function link(from, to) {
return function (args) {
return to(from(args));
};
}
function wrapConversion(toModel, graph) {
const path = [graph[toModel].parent, toModel];
let fn = conversions[graph[toModel].parent][toModel];
let cur = graph[toModel].parent;
while (graph[cur].parent) {
path.unshift(graph[cur].parent);
fn = link(conversions[graph[cur].parent][cur], fn);
cur = graph[cur].parent;
}
fn.conversion = path;
return fn;
}
export default function (fromModel) {
const graph = deriveBFS(fromModel);
const conversion = {};
const models = Object.keys(graph);
for (let len = models.length, i = 0; i < len; i++) {
const toModel = models[i];
const node = graph[toModel];
if (node.parent === null) {
// No possible conversion, or this node is the source model.
continue;
}
conversion[toModel] = wrapConversion(toModel, graph);
}
return conversion;
};

View File

@@ -0,0 +1,8 @@
The MIT License (MIT)
Copyright (c) 2015 Dmitry Ivanov
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,11 @@
A JSON with color names and its values. Based on http://dev.w3.org/csswg/css-color/#named-colors.
[![NPM](https://nodei.co/npm/color-name.png?mini=true)](https://nodei.co/npm/color-name/)
```js
var colors = require('color-name');
colors.red //[255,0,0]
```
<a href="LICENSE"><img src="https://upload.wikimedia.org/wikipedia/commons/0/0c/MIT_logo.svg" width="120"/></a>

View File

@@ -0,0 +1,152 @@
'use strict'
export default {
"aliceblue": [240, 248, 255],
"antiquewhite": [250, 235, 215],
"aqua": [0, 255, 255],
"aquamarine": [127, 255, 212],
"azure": [240, 255, 255],
"beige": [245, 245, 220],
"bisque": [255, 228, 196],
"black": [0, 0, 0],
"blanchedalmond": [255, 235, 205],
"blue": [0, 0, 255],
"blueviolet": [138, 43, 226],
"brown": [165, 42, 42],
"burlywood": [222, 184, 135],
"cadetblue": [95, 158, 160],
"chartreuse": [127, 255, 0],
"chocolate": [210, 105, 30],
"coral": [255, 127, 80],
"cornflowerblue": [100, 149, 237],
"cornsilk": [255, 248, 220],
"crimson": [220, 20, 60],
"cyan": [0, 255, 255],
"darkblue": [0, 0, 139],
"darkcyan": [0, 139, 139],
"darkgoldenrod": [184, 134, 11],
"darkgray": [169, 169, 169],
"darkgreen": [0, 100, 0],
"darkgrey": [169, 169, 169],
"darkkhaki": [189, 183, 107],
"darkmagenta": [139, 0, 139],
"darkolivegreen": [85, 107, 47],
"darkorange": [255, 140, 0],
"darkorchid": [153, 50, 204],
"darkred": [139, 0, 0],
"darksalmon": [233, 150, 122],
"darkseagreen": [143, 188, 143],
"darkslateblue": [72, 61, 139],
"darkslategray": [47, 79, 79],
"darkslategrey": [47, 79, 79],
"darkturquoise": [0, 206, 209],
"darkviolet": [148, 0, 211],
"deeppink": [255, 20, 147],
"deepskyblue": [0, 191, 255],
"dimgray": [105, 105, 105],
"dimgrey": [105, 105, 105],
"dodgerblue": [30, 144, 255],
"firebrick": [178, 34, 34],
"floralwhite": [255, 250, 240],
"forestgreen": [34, 139, 34],
"fuchsia": [255, 0, 255],
"gainsboro": [220, 220, 220],
"ghostwhite": [248, 248, 255],
"gold": [255, 215, 0],
"goldenrod": [218, 165, 32],
"gray": [128, 128, 128],
"green": [0, 128, 0],
"greenyellow": [173, 255, 47],
"grey": [128, 128, 128],
"honeydew": [240, 255, 240],
"hotpink": [255, 105, 180],
"indianred": [205, 92, 92],
"indigo": [75, 0, 130],
"ivory": [255, 255, 240],
"khaki": [240, 230, 140],
"lavender": [230, 230, 250],
"lavenderblush": [255, 240, 245],
"lawngreen": [124, 252, 0],
"lemonchiffon": [255, 250, 205],
"lightblue": [173, 216, 230],
"lightcoral": [240, 128, 128],
"lightcyan": [224, 255, 255],
"lightgoldenrodyellow": [250, 250, 210],
"lightgray": [211, 211, 211],
"lightgreen": [144, 238, 144],
"lightgrey": [211, 211, 211],
"lightpink": [255, 182, 193],
"lightsalmon": [255, 160, 122],
"lightseagreen": [32, 178, 170],
"lightskyblue": [135, 206, 250],
"lightslategray": [119, 136, 153],
"lightslategrey": [119, 136, 153],
"lightsteelblue": [176, 196, 222],
"lightyellow": [255, 255, 224],
"lime": [0, 255, 0],
"limegreen": [50, 205, 50],
"linen": [250, 240, 230],
"magenta": [255, 0, 255],
"maroon": [128, 0, 0],
"mediumaquamarine": [102, 205, 170],
"mediumblue": [0, 0, 205],
"mediumorchid": [186, 85, 211],
"mediumpurple": [147, 112, 219],
"mediumseagreen": [60, 179, 113],
"mediumslateblue": [123, 104, 238],
"mediumspringgreen": [0, 250, 154],
"mediumturquoise": [72, 209, 204],
"mediumvioletred": [199, 21, 133],
"midnightblue": [25, 25, 112],
"mintcream": [245, 255, 250],
"mistyrose": [255, 228, 225],
"moccasin": [255, 228, 181],
"navajowhite": [255, 222, 173],
"navy": [0, 0, 128],
"oldlace": [253, 245, 230],
"olive": [128, 128, 0],
"olivedrab": [107, 142, 35],
"orange": [255, 165, 0],
"orangered": [255, 69, 0],
"orchid": [218, 112, 214],
"palegoldenrod": [238, 232, 170],
"palegreen": [152, 251, 152],
"paleturquoise": [175, 238, 238],
"palevioletred": [219, 112, 147],
"papayawhip": [255, 239, 213],
"peachpuff": [255, 218, 185],
"peru": [205, 133, 63],
"pink": [255, 192, 203],
"plum": [221, 160, 221],
"powderblue": [176, 224, 230],
"purple": [128, 0, 128],
"rebeccapurple": [102, 51, 153],
"red": [255, 0, 0],
"rosybrown": [188, 143, 143],
"royalblue": [65, 105, 225],
"saddlebrown": [139, 69, 19],
"salmon": [250, 128, 114],
"sandybrown": [244, 164, 96],
"seagreen": [46, 139, 87],
"seashell": [255, 245, 238],
"sienna": [160, 82, 45],
"silver": [192, 192, 192],
"skyblue": [135, 206, 235],
"slateblue": [106, 90, 205],
"slategray": [112, 128, 144],
"slategrey": [112, 128, 144],
"snow": [255, 250, 250],
"springgreen": [0, 255, 127],
"steelblue": [70, 130, 180],
"tan": [210, 180, 140],
"teal": [0, 128, 128],
"thistle": [216, 191, 216],
"tomato": [255, 99, 71],
"turquoise": [64, 224, 208],
"violet": [238, 130, 238],
"wheat": [245, 222, 179],
"white": [255, 255, 255],
"whitesmoke": [245, 245, 245],
"yellow": [255, 255, 0],
"yellowgreen": [154, 205, 50]
};

View File

@@ -0,0 +1,28 @@
{
"name": "color-name",
"version": "1.1.4",
"description": "A list of color names and its values",
"main": "index.js",
"files": [
"index.js"
],
"scripts": {
"test": "node test.js"
},
"repository": {
"type": "git",
"url": "git@github.com:colorjs/color-name.git"
},
"keywords": [
"color-name",
"color",
"color-keyword",
"keyword"
],
"author": "DY <dfcreative@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/colorjs/color-name/issues"
},
"homepage": "https://github.com/colorjs/color-name"
}

View File

@@ -0,0 +1,21 @@
Copyright (c) 2011 Heather Arthur <fayearthur@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,62 @@
# color-string
> library for parsing and generating CSS color strings.
## Install
With [npm](http://npmjs.org/):
```console
$ npm install color-string
```
## Usage
### Parsing
```js
colorString.get('#FFF') // {model: 'rgb', value: [255, 255, 255, 1]}
colorString.get('#FFFA') // {model: 'rgb', value: [255, 255, 255, 0.67]}
colorString.get('#FFFFFFAA') // {model: 'rgb', value: [255, 255, 255, 0.67]}
colorString.get('hsl(360, 100%, 50%)') // {model: 'hsl', value: [0, 100, 50, 1]}
colorString.get('hsl(360 100% 50%)') // {model: 'hsl', value: [0, 100, 50, 1]}
colorString.get('hwb(60, 3%, 60%)') // {model: 'hwb', value: [60, 3, 60, 1]}
colorString.get.rgb('#FFF') // [255, 255, 255, 1]
colorString.get.rgb('blue') // [0, 0, 255, 1]
colorString.get.rgb('rgba(200, 60, 60, 0.3)') // [200, 60, 60, 0.3]
colorString.get.rgb('rgba(200 60 60 / 0.3)') // [200, 60, 60, 0.3]
colorString.get.rgb('rgba(200 60 60 / 30%)') // [200, 60, 60, 0.3]
colorString.get.rgb('rgb(200, 200, 200)') // [200, 200, 200, 1]
colorString.get.rgb('rgb(200 200 200)') // [200, 200, 200, 1]
colorString.get.hsl('hsl(360, 100%, 50%)') // [0, 100, 50, 1]
colorString.get.hsl('hsl(360 100% 50%)') // [0, 100, 50, 1]
colorString.get.hsl('hsla(360, 60%, 50%, 0.4)') // [0, 60, 50, 0.4]
colorString.get.hsl('hsl(360 60% 50% / 0.4)') // [0, 60, 50, 0.4]
colorString.get.hwb('hwb(60, 3%, 60%)') // [60, 3, 60, 1]
colorString.get.hwb('hwb(60, 3%, 60%, 0.6)') // [60, 3, 60, 0.6]
colorString.get.rgb('invalid color string') // null
```
### Generation
```js
colorString.to.hex([255, 255, 255]) // "#FFFFFF"
colorString.to.hex([0, 0, 255, 0.4]) // "#0000FF66"
colorString.to.hex([0, 0, 255], 0.4) // "#0000FF66"
colorString.to.rgb([255, 255, 255]) // "rgb(255, 255, 255)"
colorString.to.rgb([0, 0, 255, 0.4]) // "rgba(0, 0, 255, 0.4)"
colorString.to.rgb([0, 0, 255], 0.4) // "rgba(0, 0, 255, 0.4)"
colorString.to.rgb.percent([0, 0, 255]) // "rgb(0%, 0%, 100%)"
colorString.to.keyword([255, 255, 0]) // "yellow"
colorString.to.hsl([360, 100, 100]) // "hsl(360, 100%, 100%)"
colorString.to.hwb([50, 3, 15]) // "hwb(50, 3%, 15%)"
// all functions also support swizzling
colorString.to.rgb(0, [0, 255], 0.4) // "rgba(0, 0, 255, 0.4)"
colorString.to.rgb([0, 0], [255], 0.4) // "rgba(0, 0, 255, 0.4)"
colorString.to.rgb([0], 0, [255, 0.4]) // "rgba(0, 0, 255, 0.4)"
```

View File

@@ -0,0 +1,244 @@
/* MIT license */
import colorNames from '../color-name'
import swizzle from '../simple-swizzle'
var hasOwnProperty = Object.hasOwnProperty;
var reverseNames = Object.create(null);
// create a list of reverse color names
for (var name in colorNames) {
if (hasOwnProperty.call(colorNames, name)) {
reverseNames[colorNames[name]] = name;
}
}
var cs = {
to: {},
get: {}
};
cs.get = function (string) {
var prefix = string.substring(0, 3).toLowerCase();
var val;
var model;
switch (prefix) {
case 'hsl':
val = cs.get.hsl(string);
model = 'hsl';
break;
case 'hwb':
val = cs.get.hwb(string);
model = 'hwb';
break;
default:
val = cs.get.rgb(string);
model = 'rgb';
break;
}
if (!val) {
return null;
}
return {model: model, value: val};
};
cs.get.rgb = function (string) {
if (!string) {
return null;
}
var abbr = /^#([a-f0-9]{3,4})$/i;
var hex = /^#([a-f0-9]{6})([a-f0-9]{2})?$/i;
var rgba = /^rgba?\(\s*([+-]?\d+)(?=[\s,])\s*(?:,\s*)?([+-]?\d+)(?=[\s,])\s*(?:,\s*)?([+-]?\d+)\s*(?:[,|\/]\s*([+-]?[\d\.]+)(%?)\s*)?\)$/;
var per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,?\s*([+-]?[\d\.]+)\%\s*,?\s*([+-]?[\d\.]+)\%\s*(?:[,|\/]\s*([+-]?[\d\.]+)(%?)\s*)?\)$/;
var keyword = /^(\w+)$/;
var rgb = [0, 0, 0, 1];
var match;
var i;
var hexAlpha;
if (match = string.match(hex)) {
hexAlpha = match[2];
match = match[1];
for (i = 0; i < 3; i++) {
// https://jsperf.com/slice-vs-substr-vs-substring-methods-long-string/19
var i2 = i * 2;
rgb[i] = parseInt(match.slice(i2, i2 + 2), 16);
}
if (hexAlpha) {
rgb[3] = parseInt(hexAlpha, 16) / 255;
}
} else if (match = string.match(abbr)) {
match = match[1];
hexAlpha = match[3];
for (i = 0; i < 3; i++) {
rgb[i] = parseInt(match[i] + match[i], 16);
}
if (hexAlpha) {
rgb[3] = parseInt(hexAlpha + hexAlpha, 16) / 255;
}
} else if (match = string.match(rgba)) {
for (i = 0; i < 3; i++) {
rgb[i] = parseInt(match[i + 1], 0);
}
if (match[4]) {
if (match[5]) {
rgb[3] = parseFloat(match[4]) * 0.01;
} else {
rgb[3] = parseFloat(match[4]);
}
}
} else if (match = string.match(per)) {
for (i = 0; i < 3; i++) {
rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55);
}
if (match[4]) {
if (match[5]) {
rgb[3] = parseFloat(match[4]) * 0.01;
} else {
rgb[3] = parseFloat(match[4]);
}
}
} else if (match = string.match(keyword)) {
if (match[1] === 'transparent') {
return [0, 0, 0, 0];
}
if (!hasOwnProperty.call(colorNames, match[1])) {
return null;
}
rgb = colorNames[match[1]];
rgb[3] = 1;
return rgb;
} else {
return null;
}
for (i = 0; i < 3; i++) {
rgb[i] = clamp(rgb[i], 0, 255);
}
rgb[3] = clamp(rgb[3], 0, 1);
return rgb;
};
cs.get.hsl = function (string) {
if (!string) {
return null;
}
var hsl = /^hsla?\(\s*([+-]?(?:\d{0,3}\.)?\d+)(?:deg)?\s*,?\s*([+-]?[\d\.]+)%\s*,?\s*([+-]?[\d\.]+)%\s*(?:[,|\/]\s*([+-]?(?=\.\d|\d)(?:0|[1-9]\d*)?(?:\.\d*)?(?:[eE][+-]?\d+)?)\s*)?\)$/;
var match = string.match(hsl);
if (match) {
var alpha = parseFloat(match[4]);
var h = ((parseFloat(match[1]) % 360) + 360) % 360;
var s = clamp(parseFloat(match[2]), 0, 100);
var l = clamp(parseFloat(match[3]), 0, 100);
var a = clamp(isNaN(alpha) ? 1 : alpha, 0, 1);
return [h, s, l, a];
}
return null;
};
cs.get.hwb = function (string) {
if (!string) {
return null;
}
var hwb = /^hwb\(\s*([+-]?\d{0,3}(?:\.\d+)?)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?(?=\.\d|\d)(?:0|[1-9]\d*)?(?:\.\d*)?(?:[eE][+-]?\d+)?)\s*)?\)$/;
var match = string.match(hwb);
if (match) {
var alpha = parseFloat(match[4]);
var h = ((parseFloat(match[1]) % 360) + 360) % 360;
var w = clamp(parseFloat(match[2]), 0, 100);
var b = clamp(parseFloat(match[3]), 0, 100);
var a = clamp(isNaN(alpha) ? 1 : alpha, 0, 1);
return [h, w, b, a];
}
return null;
};
cs.to.hex = function () {
var rgba = swizzle(arguments);
return (
'#' +
hexDouble(rgba[0]) +
hexDouble(rgba[1]) +
hexDouble(rgba[2]) +
(rgba[3] < 1
? (hexDouble(Math.round(rgba[3] * 255)))
: '')
);
};
cs.to.rgb = function () {
var rgba = swizzle(arguments);
return rgba.length < 4 || rgba[3] === 1
? 'rgb(' + Math.round(rgba[0]) + ', ' + Math.round(rgba[1]) + ', ' + Math.round(rgba[2]) + ')'
: 'rgba(' + Math.round(rgba[0]) + ', ' + Math.round(rgba[1]) + ', ' + Math.round(rgba[2]) + ', ' + rgba[3] + ')';
};
cs.to.rgb.percent = function () {
var rgba = swizzle(arguments);
var r = Math.round(rgba[0] / 255 * 100);
var g = Math.round(rgba[1] / 255 * 100);
var b = Math.round(rgba[2] / 255 * 100);
return rgba.length < 4 || rgba[3] === 1
? 'rgb(' + r + '%, ' + g + '%, ' + b + '%)'
: 'rgba(' + r + '%, ' + g + '%, ' + b + '%, ' + rgba[3] + ')';
};
cs.to.hsl = function () {
var hsla = swizzle(arguments);
return hsla.length < 4 || hsla[3] === 1
? 'hsl(' + hsla[0] + ', ' + hsla[1] + '%, ' + hsla[2] + '%)'
: 'hsla(' + hsla[0] + ', ' + hsla[1] + '%, ' + hsla[2] + '%, ' + hsla[3] + ')';
};
// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
// (hwb have alpha optional & 1 is default value)
cs.to.hwb = function () {
var hwba = swizzle(arguments);
var a = '';
if (hwba.length >= 4 && hwba[3] !== 1) {
a = ', ' + hwba[3];
}
return 'hwb(' + hwba[0] + ', ' + hwba[1] + '%, ' + hwba[2] + '%' + a + ')';
};
cs.to.keyword = function (rgb) {
return reverseNames[rgb.slice(0, 3)];
};
// helpers
function clamp(num, min, max) {
return Math.min(Math.max(min, num), max);
}
function hexDouble(num) {
var str = Math.round(num).toString(16).toUpperCase();
return (str.length < 2) ? '0' + str : str;
}
export default cs;

View File

@@ -0,0 +1,39 @@
{
"name": "color-string",
"description": "Parser and generator for CSS color strings",
"version": "1.9.1",
"author": "Heather Arthur <fayearthur@gmail.com>",
"contributors": [
"Maxime Thirouin",
"Dyma Ywanov <dfcreative@gmail.com>",
"Josh Junon"
],
"repository": "Qix-/color-string",
"scripts": {
"pretest": "xo",
"test": "node test/basic.js"
},
"license": "MIT",
"files": [
"index.js"
],
"xo": {
"rules": {
"no-cond-assign": 0,
"operator-linebreak": 0
}
},
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
},
"devDependencies": {
"xo": "^0.12.1"
},
"keywords": [
"color",
"colour",
"rgb",
"css"
]
}

View File

@@ -0,0 +1,496 @@
import colorString from './color-string'
import convert from './color-convert'
const skippedModels = [
// To be honest, I don't really feel like keyword belongs in color convert, but eh.
'keyword',
// Gray conflicts with some method names, and has its own method defined.
'gray',
// Shouldn't really be in color-convert either...
'hex',
];
const hashedModelKeys = {};
for (const model of Object.keys(convert)) {
hashedModelKeys[[...convert[model].labels].sort().join('')] = model;
}
const limiters = {};
function Color(object, model) {
if (!(this instanceof Color)) {
return new Color(object, model);
}
if (model && model in skippedModels) {
model = null;
}
if (model && !(model in convert)) {
throw new Error('Unknown model: ' + model);
}
let i;
let channels;
if (object == null) { // eslint-disable-line no-eq-null,eqeqeq
this.model = 'rgb';
this.color = [0, 0, 0];
this.valpha = 1;
} else if (object instanceof Color) {
this.model = object.model;
this.color = [...object.color];
this.valpha = object.valpha;
} else if (typeof object === 'string') {
const result = colorString.get(object);
if (result === null) {
throw new Error('Unable to parse color from string: ' + object);
}
this.model = result.model;
channels = convert[this.model].channels;
this.color = result.value.slice(0, channels);
this.valpha = typeof result.value[channels] === 'number' ? result.value[channels] : 1;
} else if (object.length > 0) {
this.model = model || 'rgb';
channels = convert[this.model].channels;
const newArray = Array.prototype.slice.call(object, 0, channels);
this.color = zeroArray(newArray, channels);
this.valpha = typeof object[channels] === 'number' ? object[channels] : 1;
} else if (typeof object === 'number') {
// This is always RGB - can be converted later on.
this.model = 'rgb';
this.color = [
(object >> 16) & 0xFF,
(object >> 8) & 0xFF,
object & 0xFF,
];
this.valpha = 1;
} else {
this.valpha = 1;
const keys = Object.keys(object);
if ('alpha' in object) {
keys.splice(keys.indexOf('alpha'), 1);
this.valpha = typeof object.alpha === 'number' ? object.alpha : 0;
}
const hashedKeys = keys.sort().join('');
if (!(hashedKeys in hashedModelKeys)) {
throw new Error('Unable to parse color from object: ' + JSON.stringify(object));
}
this.model = hashedModelKeys[hashedKeys];
const {labels} = convert[this.model];
const color = [];
for (i = 0; i < labels.length; i++) {
color.push(object[labels[i]]);
}
this.color = zeroArray(color);
}
// Perform limitations (clamping, etc.)
if (limiters[this.model]) {
channels = convert[this.model].channels;
for (i = 0; i < channels; i++) {
const limit = limiters[this.model][i];
if (limit) {
this.color[i] = limit(this.color[i]);
}
}
}
this.valpha = Math.max(0, Math.min(1, this.valpha));
if (Object.freeze) {
Object.freeze(this);
}
}
Color.prototype = {
toString() {
return this.string();
},
toJSON() {
return this[this.model]();
},
string(places) {
let self = this.model in colorString.to ? this : this.rgb();
self = self.round(typeof places === 'number' ? places : 1);
const args = self.valpha === 1 ? self.color : [...self.color, this.valpha];
return colorString.to[self.model](args);
},
percentString(places) {
const self = this.rgb().round(typeof places === 'number' ? places : 1);
const args = self.valpha === 1 ? self.color : [...self.color, this.valpha];
return colorString.to.rgb.percent(args);
},
array() {
return this.valpha === 1 ? [...this.color] : [...this.color, this.valpha];
},
object() {
const result = {};
const {channels} = convert[this.model];
const {labels} = convert[this.model];
for (let i = 0; i < channels; i++) {
result[labels[i]] = this.color[i];
}
if (this.valpha !== 1) {
result.alpha = this.valpha;
}
return result;
},
unitArray() {
const rgb = this.rgb().color;
rgb[0] /= 255;
rgb[1] /= 255;
rgb[2] /= 255;
if (this.valpha !== 1) {
rgb.push(this.valpha);
}
return rgb;
},
unitObject() {
const rgb = this.rgb().object();
rgb.r /= 255;
rgb.g /= 255;
rgb.b /= 255;
if (this.valpha !== 1) {
rgb.alpha = this.valpha;
}
return rgb;
},
round(places) {
places = Math.max(places || 0, 0);
return new Color([...this.color.map(roundToPlace(places)), this.valpha], this.model);
},
alpha(value) {
if (value !== undefined) {
return new Color([...this.color, Math.max(0, Math.min(1, value))], this.model);
}
return this.valpha;
},
// Rgb
red: getset('rgb', 0, maxfn(255)),
green: getset('rgb', 1, maxfn(255)),
blue: getset('rgb', 2, maxfn(255)),
hue: getset(['hsl', 'hsv', 'hsl', 'hwb', 'hcg'], 0, value => ((value % 360) + 360) % 360),
saturationl: getset('hsl', 1, maxfn(100)),
lightness: getset('hsl', 2, maxfn(100)),
saturationv: getset('hsv', 1, maxfn(100)),
value: getset('hsv', 2, maxfn(100)),
chroma: getset('hcg', 1, maxfn(100)),
gray: getset('hcg', 2, maxfn(100)),
white: getset('hwb', 1, maxfn(100)),
wblack: getset('hwb', 2, maxfn(100)),
cyan: getset('cmyk', 0, maxfn(100)),
magenta: getset('cmyk', 1, maxfn(100)),
yellow: getset('cmyk', 2, maxfn(100)),
black: getset('cmyk', 3, maxfn(100)),
x: getset('xyz', 0, maxfn(95.047)),
y: getset('xyz', 1, maxfn(100)),
z: getset('xyz', 2, maxfn(108.833)),
l: getset('lab', 0, maxfn(100)),
a: getset('lab', 1),
b: getset('lab', 2),
keyword(value) {
if (value !== undefined) {
return new Color(value);
}
return convert[this.model].keyword(this.color);
},
hex(value) {
if (value !== undefined) {
return new Color(value);
}
return colorString.to.hex(this.rgb().round().color);
},
hexa(value) {
if (value !== undefined) {
return new Color(value);
}
const rgbArray = this.rgb().round().color;
let alphaHex = Math.round(this.valpha * 255).toString(16).toUpperCase();
if (alphaHex.length === 1) {
alphaHex = '0' + alphaHex;
}
return colorString.to.hex(rgbArray) + alphaHex;
},
rgbNumber() {
const rgb = this.rgb().color;
return ((rgb[0] & 0xFF) << 16) | ((rgb[1] & 0xFF) << 8) | (rgb[2] & 0xFF);
},
luminosity() {
// http://www.w3.org/TR/WCAG20/#relativeluminancedef
const rgb = this.rgb().color;
const lum = [];
for (const [i, element] of rgb.entries()) {
const chan = element / 255;
lum[i] = (chan <= 0.04045) ? chan / 12.92 : ((chan + 0.055) / 1.055) ** 2.4;
}
return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
},
contrast(color2) {
// http://www.w3.org/TR/WCAG20/#contrast-ratiodef
const lum1 = this.luminosity();
const lum2 = color2.luminosity();
if (lum1 > lum2) {
return (lum1 + 0.05) / (lum2 + 0.05);
}
return (lum2 + 0.05) / (lum1 + 0.05);
},
level(color2) {
// https://www.w3.org/TR/WCAG/#contrast-enhanced
const contrastRatio = this.contrast(color2);
if (contrastRatio >= 7) {
return 'AAA';
}
return (contrastRatio >= 4.5) ? 'AA' : '';
},
isDark() {
// YIQ equation from http://24ways.org/2010/calculating-color-contrast
const rgb = this.rgb().color;
const yiq = (rgb[0] * 2126 + rgb[1] * 7152 + rgb[2] * 722) / 10000;
return yiq < 128;
},
isLight() {
return !this.isDark();
},
negate() {
const rgb = this.rgb();
for (let i = 0; i < 3; i++) {
rgb.color[i] = 255 - rgb.color[i];
}
return rgb;
},
lighten(ratio) {
const hsl = this.hsl();
hsl.color[2] += hsl.color[2] * ratio;
return hsl;
},
darken(ratio) {
const hsl = this.hsl();
hsl.color[2] -= hsl.color[2] * ratio;
return hsl;
},
saturate(ratio) {
const hsl = this.hsl();
hsl.color[1] += hsl.color[1] * ratio;
return hsl;
},
desaturate(ratio) {
const hsl = this.hsl();
hsl.color[1] -= hsl.color[1] * ratio;
return hsl;
},
whiten(ratio) {
const hwb = this.hwb();
hwb.color[1] += hwb.color[1] * ratio;
return hwb;
},
blacken(ratio) {
const hwb = this.hwb();
hwb.color[2] += hwb.color[2] * ratio;
return hwb;
},
grayscale() {
// http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
const rgb = this.rgb().color;
const value = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;
return Color.rgb(value, value, value);
},
fade(ratio) {
return this.alpha(this.valpha - (this.valpha * ratio));
},
opaquer(ratio) {
return this.alpha(this.valpha + (this.valpha * ratio));
},
rotate(degrees) {
const hsl = this.hsl();
let hue = hsl.color[0];
hue = (hue + degrees) % 360;
hue = hue < 0 ? 360 + hue : hue;
hsl.color[0] = hue;
return hsl;
},
mix(mixinColor, weight) {
// Ported from sass implementation in C
// https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209
if (!mixinColor || !mixinColor.rgb) {
throw new Error('Argument to "mix" was not a Color instance, but rather an instance of ' + typeof mixinColor);
}
const color1 = mixinColor.rgb();
const color2 = this.rgb();
const p = weight === undefined ? 0.5 : weight;
const w = 2 * p - 1;
const a = color1.alpha() - color2.alpha();
const w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2;
const w2 = 1 - w1;
return Color.rgb(
w1 * color1.red() + w2 * color2.red(),
w1 * color1.green() + w2 * color2.green(),
w1 * color1.blue() + w2 * color2.blue(),
color1.alpha() * p + color2.alpha() * (1 - p));
},
};
// Model conversion methods and static constructors
for (const model of Object.keys(convert)) {
if (skippedModels.includes(model)) {
continue;
}
const {channels} = convert[model];
// Conversion methods
Color.prototype[model] = function (...args) {
if (this.model === model) {
return new Color(this);
}
if (args.length > 0) {
return new Color(args, model);
}
return new Color([...assertArray(convert[this.model][model].raw(this.color)), this.valpha], model);
};
// 'static' construction methods
Color[model] = function (...args) {
let color = args[0];
if (typeof color === 'number') {
color = zeroArray(args, channels);
}
return new Color(color, model);
};
}
function roundTo(number, places) {
return Number(number.toFixed(places));
}
function roundToPlace(places) {
return function (number) {
return roundTo(number, places);
};
}
function getset(model, channel, modifier) {
model = Array.isArray(model) ? model : [model];
for (const m of model) {
(limiters[m] || (limiters[m] = []))[channel] = modifier;
}
model = model[0];
return function (value) {
let result;
if (value !== undefined) {
if (modifier) {
value = modifier(value);
}
result = this[model]();
result.color[channel] = value;
return result;
}
result = this[model]().color[channel];
if (modifier) {
result = modifier(result);
}
return result;
};
}
function maxfn(max) {
return function (v) {
return Math.max(0, Math.min(max, v));
};
}
function assertArray(value) {
return Array.isArray(value) ? value : [value];
}
function zeroArray(array, length) {
for (let i = 0; i < length; i++) {
if (typeof array[i] !== 'number') {
array[i] = 0;
}
}
return array;
}
export default Color;

View File

@@ -0,0 +1,158 @@
import Color from './color';
/**
* 转换颜色格式。
* @param {Object} params - 参数对象。
* @param {string} color - 输入的颜色,默认为 '#fff'。
* @param {string} format - 需要转换的格式(支持 'rgb', 'hex', 'hsl', 'hsv', 'hwb')。
* @param {string} type - 转换后的类型(支持 'string', 'object', 'array', 'round')。
* @returns {string|Object|Array} 转换后的颜色表示。
*/
function convertFormat(color = '#fff', format = 'rgb', type = 'string') {
let colorObj = Color(color);
// 如果格式存在
if (colorObj[format]) {
// hex 无法直接转换为 除string类型外的任何类型
// 所以转为rgb 后 获取其他类型
if(format == 'hex' && type != 'string') format = 'rgb';
// 类型名称
let typeName = '';
switch (type) {
case 'string':
typeName = 'toString';
break;
case 'object':
typeName = 'object';
break;
case 'array':
typeName = 'array';
break;
case 'round':
typeName = 'round';
break;
default:
throw Error('Unsupported target type:' + type)
}
return colorObj[format]()[typeName]();
} else {
throw Error('Unsupported target format: ' + format);
}
}
/**
* 计算两个颜色之间的渐变值。
* @param {string} startColor - 开始的颜色,默认为黑色。
* @param {string} endColor - 结束的颜色,默认为白色。
* @param {number} step - 渐变的步数默认为10。
* @returns {Array<string>} 两个颜色之间的渐变颜色数组。
*/
function gradient(startColor = 'rgb(0, 0, 0)', endColor = 'rgb(255, 255, 255)', step = 10) {
const startRGB = convertFormat(startColor, 'rgb', 'array') // 转换为rgb数组模式
const startR = startRGB[0]
const startG = startRGB[1]
const startB = startRGB[2]
const endRGB = convertFormat(endColor, 'rgb', 'array')
const endR = endRGB[0]
const endG = endRGB[1]
const endB = endRGB[2]
const sR = (endR - startR) / step // 总差值
const sG = (endG - startG) / step
const sB = (endB - startB) / step
const colorArr = []
for (let i = 0; i < step; i++) {
// 计算每一步的hex值
let hex = convertFormat(`rgb(${Math.round((sR * i + startR))},${Math.round((sG * i + startG))},${Math.round((sB
* i + startB))})`, 'hex')
// 确保第一个颜色值为startColor的值
if (i === 0) hex = convertFormat(startColor, 'hex')
// 确保最后一个颜色值为endColor的值
if (i === step - 1) hex = convertFormat(endColor, 'hex')
colorArr.push(hex)
}
return colorArr
}
export default {
/**
* 格式转换。
*/
convertFormat,
/**
* 计算两个颜色之间的渐变值。
*/
gradient,
/**
* 增加颜色的亮度。
* @param {string} color - 输入的颜色。
* @param {number} value - 增加的亮度值0-1
* @returns {string} 调整后的颜色。
*/
lighten: (color, value, format = 'rgb', type = 'string') => convertFormat(Color(color).lighten(value), format, type),
/**
* 减少颜色的亮度。
* @param {string} color - 输入的颜色。
* @param {number} value - 减少的亮度值0-1
* @returns {string} 调整后的颜色。
*/
darken: (color, value, format = 'rgb', type = 'string') => convertFormat(Color(color).darken(value), format, type),
/**
* 增加颜色的饱和度。
* @param {string} color - 输入的颜色。
* @param {number} value - 增加的饱和度值0-1
* @returns {string} 调整后的颜色。
*/
saturate: (color, value, format = 'rgb', type = 'string') => convertFormat(Color(color).saturate(value), format, type),
/**
* 减少颜色的饱和度。
* @param {string} color - 输入的颜色。
* @param {number} value - 减少的饱和度值0-1
* @returns {string} 调整后的颜色。
*/
desaturate: (color, value, format = 'rgb', type = 'string') => convertFormat(Color(color).desaturate(value), format, type),
/**
* 旋转颜色的色相。
* @param {string} color - 输入的颜色。
* @param {number} degrees - 旋转的度数。
* @returns {string} 调整后的颜色。
*/
rotate: (color, degrees, format = 'rgb', type = 'string') => convertFormat(Color(color).rotate(degrees), format, type),
/**
* 调整颜色的透明度。
* @param {string} color - 输入的颜色。
* @param {number} value - 透明度值0-1其中 1 是不透明)。
* @returns {string} 调整后的颜色。
*/
adjustAlpha: (color, value, format = 'rgb', type = 'string') => convertFormat(Color(color).alpha(value), format, type),
/**
* 获取颜色的亮度。
* @param {string} color - 输入的颜色。
* @returns {number} 颜色的亮度值0-1
*/
luminosity: (color, format) => Color(color).luminosity(),
/**
* 判断颜色是否为暗色。
* @param {string} color - 输入的颜色。
* @returns {boolean} 如果是暗色则返回 true否则返回 false。
*/
isDark: (color, format) => Color(color).isDark(),
/**
* 判断颜色是否为亮色。
* @param {string} color - 输入的颜色。
* @returns {boolean} 如果是亮色则返回 true否则返回 false。
*/
isLight: (color, format) => Color(color).isLight()
};

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 JD Ballard
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,16 @@
# node-is-arrayish [![Travis-CI.org Build Status](https://img.shields.io/travis/Qix-/node-is-arrayish.svg?style=flat-square)](https://travis-ci.org/Qix-/node-is-arrayish) [![Coveralls.io Coverage Rating](https://img.shields.io/coveralls/Qix-/node-is-arrayish.svg?style=flat-square)](https://coveralls.io/r/Qix-/node-is-arrayish)
> Determines if an object can be used like an Array
## Example
```javascript
var isArrayish = require('is-arrayish');
isArrayish([]); // true
isArrayish({__proto__: []}); // true
isArrayish({}); // false
isArrayish({length:10}); // false
```
## License
Licensed under the [MIT License](http://opensource.org/licenses/MIT).
You can find a copy of it in [LICENSE](LICENSE).

View File

@@ -0,0 +1,9 @@
export default function isArrayish(obj) {
if (!obj || typeof obj === 'string') {
return false;
}
return obj instanceof Array || Array.isArray(obj) ||
(obj.length >= 0 && (obj.splice instanceof Function ||
(Object.getOwnPropertyDescriptor(obj, (obj.length - 1)) && obj.constructor.name !== 'String')));
};

View File

@@ -0,0 +1,45 @@
{
"name": "is-arrayish",
"description": "Determines if an object can be used as an array",
"version": "0.3.2",
"author": "Qix (http://github.com/qix-)",
"keywords": [
"is",
"array",
"duck",
"type",
"arrayish",
"similar",
"proto",
"prototype",
"type"
],
"license": "MIT",
"scripts": {
"test": "mocha --require coffeescript/register ./test/**/*.coffee",
"lint": "zeit-eslint --ext .jsx,.js .",
"lint-staged": "git diff --diff-filter=ACMRT --cached --name-only '*.js' '*.jsx' | xargs zeit-eslint"
},
"repository": {
"type": "git",
"url": "https://github.com/qix-/node-is-arrayish.git"
},
"devDependencies": {
"@zeit/eslint-config-node": "^0.3.0",
"@zeit/git-hooks": "^0.1.4",
"coffeescript": "^2.3.1",
"coveralls": "^3.0.1",
"eslint": "^4.19.1",
"istanbul": "^0.4.5",
"mocha": "^5.2.0",
"should": "^13.2.1"
},
"eslintConfig": {
"extends": [
"@zeit/eslint-config-node"
]
},
"git": {
"pre-commit": "lint-staged"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Josh Junon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,39 @@
# simple-swizzle [![Travis-CI.org Build Status](https://img.shields.io/travis/Qix-/node-simple-swizzle.svg?style=flat-square)](https://travis-ci.org/Qix-/node-simple-swizzle) [![Coveralls.io Coverage Rating](https://img.shields.io/coveralls/Qix-/node-simple-swizzle.svg?style=flat-square)](https://coveralls.io/r/Qix-/node-simple-swizzle)
> [Swizzle](https://en.wikipedia.org/wiki/Swizzling_(computer_graphics)) your function arguments; pass in mixed arrays/values and get a clean array
## Usage
```js
var swizzle = require('simple-swizzle');
function myFunc() {
var args = swizzle(arguments);
// ...
return args;
}
myFunc(1, [2, 3], 4); // [1, 2, 3, 4]
myFunc(1, 2, 3, 4); // [1, 2, 3, 4]
myFunc([1, 2, 3, 4]); // [1, 2, 3, 4]
```
Functions can also be wrapped to automatically swizzle arguments and be passed
the resulting array.
```js
var swizzle = require('simple-swizzle');
var swizzledFn = swizzle.wrap(function (args) {
// ...
return args;
});
swizzledFn(1, [2, 3], 4); // [1, 2, 3, 4]
swizzledFn(1, 2, 3, 4); // [1, 2, 3, 4]
swizzledFn([1, 2, 3, 4]); // [1, 2, 3, 4]
```
## License
Licensed under the [MIT License](http://opensource.org/licenses/MIT).
You can find a copy of it in [LICENSE](LICENSE).

View File

@@ -0,0 +1,29 @@
'use strict';
import isArrayish from '../is-arrayish';
var concat = Array.prototype.concat;
var slice = Array.prototype.slice;
export default function swizzle(args) {
var results = [];
for (var i = 0, len = args.length; i < len; i++) {
var arg = args[i];
if (isArrayish(arg)) {
// http://jsperf.com/javascript-array-concat-vs-push/98
results = concat.call(results, slice.call(arg));
} else {
results.push(arg);
}
}
return results;
};
swizzle.wrap = function (fn) {
return function () {
return fn(swizzle(arguments));
};
};

View File

@@ -0,0 +1,36 @@
{
"name": "simple-swizzle",
"description": "Simply swizzle your arguments",
"version": "0.2.2",
"author": "Qix (http://github.com/qix-)",
"keywords": [
"argument",
"arguments",
"swizzle",
"swizzling",
"parameter",
"parameters",
"mixed",
"array"
],
"license": "MIT",
"scripts": {
"pretest": "xo",
"test": "mocha --compilers coffee:coffee-script/register"
},
"files": [
"index.js"
],
"repository": "qix-/node-simple-swizzle",
"devDependencies": {
"coffee-script": "^1.9.3",
"coveralls": "^2.11.2",
"istanbul": "^0.3.17",
"mocha": "^2.2.5",
"should": "^7.0.1",
"xo": "^0.7.1"
},
"dependencies": {
"is-arrayish": "^0.3.1"
}
}

View File

@@ -0,0 +1,29 @@
let timeout = null
/**
* 防抖原理一定时间内只有最后一次操作再过wait毫秒后才执行函数
*
* @param {Function} func 要执行的回调函数
* @param {Number} wait 延时的时间
* @param {Boolean} immediate 是否立即执行
* @return null
*/
function debounce(func, wait = 500, immediate = false) {
// 清除定时器
if (timeout !== null) clearTimeout(timeout)
// 立即执行,此类情况一般用不到
if (immediate) {
const callNow = !timeout
timeout = setTimeout(() => {
timeout = null
}, wait)
if (callNow) typeof func === 'function' && func()
} else {
// 设置定时器当最后一次操作后timeout不会再被清除所以在延时wait毫秒后执行func回调方法
timeout = setTimeout(() => {
typeof func === 'function' && func()
}, wait)
}
}
export default debounce

View File

@@ -0,0 +1,167 @@
let _boundaryCheckingState = true; // 是否进行越界检查的全局开关
/**
* 把错误的数据转正
* @private
* @example strip(0.09999999999999998)=0.1
*/
function strip(num, precision = 15) {
return +parseFloat(Number(num).toPrecision(precision));
}
/**
* Return digits length of a number
* @private
* @param {*number} num Input number
*/
function digitLength(num) {
// Get digit length of e
const eSplit = num.toString().split(/[eE]/);
const len = (eSplit[0].split('.')[1] || '').length - +(eSplit[1] || 0);
return len > 0 ? len : 0;
}
/**
* 把小数转成整数,如果是小数则放大成整数
* @private
* @param {*number} num 输入数
*/
function float2Fixed(num) {
if (num.toString().indexOf('e') === -1) {
return Number(num.toString().replace('.', ''));
}
const dLen = digitLength(num);
return dLen > 0 ? strip(Number(num) * Math.pow(10, dLen)) : Number(num);
}
/**
* 检测数字是否越界,如果越界给出提示
* @private
* @param {*number} num 输入数
*/
function checkBoundary(num) {
if (_boundaryCheckingState) {
if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) {
console.warn(`${num} 超出了精度限制,结果可能不正确`);
}
}
}
/**
* 把递归操作扁平迭代化
* @param {number[]} arr 要操作的数字数组
* @param {function} operation 迭代操作
* @private
*/
function iteratorOperation(arr, operation) {
const [num1, num2, ...others] = arr;
let res = operation(num1, num2);
others.forEach((num) => {
res = operation(res, num);
});
return res;
}
/**
* 高精度乘法
* @export
*/
export function times(...nums) {
if (nums.length > 2) {
return iteratorOperation(nums, times);
}
const [num1, num2] = nums;
const num1Changed = float2Fixed(num1);
const num2Changed = float2Fixed(num2);
const baseNum = digitLength(num1) + digitLength(num2);
const leftValue = num1Changed * num2Changed;
checkBoundary(leftValue);
return leftValue / Math.pow(10, baseNum);
}
/**
* 高精度加法
* @export
*/
export function plus(...nums) {
if (nums.length > 2) {
return iteratorOperation(nums, plus);
}
const [num1, num2] = nums;
// 取最大的小数位
const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)));
// 把小数都转为整数然后再计算
return (times(num1, baseNum) + times(num2, baseNum)) / baseNum;
}
/**
* 高精度减法
* @export
*/
export function minus(...nums) {
if (nums.length > 2) {
return iteratorOperation(nums, minus);
}
const [num1, num2] = nums;
const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)));
return (times(num1, baseNum) - times(num2, baseNum)) / baseNum;
}
/**
* 高精度除法
* @export
*/
export function divide(...nums) {
if (nums.length > 2) {
return iteratorOperation(nums, divide);
}
const [num1, num2] = nums;
const num1Changed = float2Fixed(num1);
const num2Changed = float2Fixed(num2);
checkBoundary(num1Changed);
checkBoundary(num2Changed);
// 重要这里必须用strip进行修正
return times(num1Changed / num2Changed, strip(Math.pow(10, digitLength(num2) - digitLength(num1))));
}
/**
* 四舍五入
* @export
*/
export function round(num, ratio) {
const base = Math.pow(10, ratio);
let result = divide(Math.round(Math.abs(times(num, base))), base);
if (num < 0 && result !== 0) {
result = times(result, -1);
}
// 位数不足则补0
return result;
}
/**
* 是否进行边界检查,默认开启
* @param flag 标记开关true 为开启false 为关闭,默认为 true
* @export
*/
export function enableBoundaryChecking(flag = true) {
_boundaryCheckingState = flag;
}
export default {
times,
plus,
minus,
divide,
round,
enableBoundaryChecking,
};

View File

@@ -0,0 +1,738 @@
import { number, empty } from './test.js'
import { round } from './digit.js'
// 颜色操作方法
import Color from './color'
/**
* @description 如果value小于min取min如果value大于max取max
* @param {number} min
* @param {number} max
* @param {number} value
*/
function range(min = 0, max = 0, value = 0) {
return Math.max(min, Math.min(max, Number(value)))
}
/**
* @description 用于获取用户传递值的px值 如果用户传递了"xxpx"或者"xxrpx",取出其数值部分,如果是"xxxrpx"还需要用过uni.upx2px进行转换
* @param {number|string} value 用户传递值的px值
* @param {boolean} unit
* @returns {number|string}
*/
function getPx(value, unit = false) {
if (number(value)) {
return unit ? `${value}px` : Number(value)
}
// 如果带有rpx先取出其数值部分再转为px值
if (/(rpx|upx)$/.test(value)) {
return unit ? `${uni.upx2px(parseInt(value))}px` : Number(uni.upx2px(parseInt(value)))
}
return unit ? `${parseInt(value)}px` : parseInt(value)
}
/**
* @description 进行延时,以达到可以简写代码的目的 比如: await uni.$w.sleep(20)将会阻塞20ms
* @param {number} value 堵塞时间 单位ms 毫秒
* @returns {Promise} 返回promise
*/
function sleep(value = 30) {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, value)
})
}
/**
* @description 运行期判断平台
* @returns {string} 返回所在平台(小写)
* @link 运行期判断平台 https://uniapp.dcloud.io/frame?id=判断平台
*/
function os() {
return uni.getSystemInfoSync().platform.toLowerCase()
}
/**
* @description 获取系统信息同步接口
* @link 获取系统信息同步接口 https://uniapp.dcloud.io/api/system/info?id=getsysteminfosync
*/
function sys() {
return uni.getSystemInfoSync()
}
/**
* @description 取一个区间数
* @param {Number} min 最小值
* @param {Number} max 最大值
*/
function random(min, max) {
if (min >= 0 && max > 0 && max >= min) {
const gab = max - min + 1
return Math.floor(Math.random() * gab + min)
}
return 0
}
/**
* @param {Number} len uuid的长度
* @param {Boolean} firstU 将返回的首字母置为"u"
* @param {Nubmer} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制
*/
function guid(len = 32, firstU = true, radix = null) {
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')
const uuid = []
radix = radix || chars.length
if (len) {
// 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]
} else {
let r
// rfc4122标准要求返回的uuid中,某些位为固定的字符
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'
uuid[14] = '4'
for (let i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | Math.random() * 16
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]
}
}
}
// 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class
if (firstU) {
uuid.shift()
return `u${uuid.join('')}`
}
return uuid.join('')
}
/**
* @description 获取父组件的参数因为支付宝小程序不支持provide/inject的写法
this.$parent在非H5中可以准确获取到父组件但是在H5中需要多次this.$parent.$parent.xxx
这里默认值等于undefined有它的含义因为最顶层元素(组件)的$parent就是undefined意味着不传name
值(默认为undefined),就是查找最顶层的$parent
* @param {string|undefined} name 父组件的参数名
*/
function $parent(name = undefined) {
let parent = this.$parent
// 通过while历遍这里主要是为了H5需要多层解析的问题
while (parent) {
// 父组件
if (parent.$options && parent.$options.name !== name) {
// 如果组件的name不相等继续上一级寻找
parent = parent.$parent
} else {
return parent
}
}
return false
}
/**
* @description 样式转换
* 对象转字符串,或者字符串转对象
* @param {object | string} customStyle 需要转换的目标
* @param {String} target 转换的目的object-转为对象string-转为字符串
* @returns {object|string}
*/
function addStyle(customStyle, target = 'object') {
// 字符串转字符串,对象转对象情形,直接返回
if (empty(customStyle) || typeof(customStyle) === 'object' && target === 'object' || target === 'string' &&
typeof(customStyle) === 'string') {
return customStyle
}
// 字符串转对象
if (target === 'object') {
// 去除字符串样式中的两端空格(中间的空格不能去掉比如padding: 20px 0如果去掉了就错了),空格是无用的
customStyle = trim(customStyle)
// 根据";"将字符串转为数组形式
const styleArray = customStyle.split(';')
const style = {}
// 历遍数组,拼接成对象
for (let i = 0; i < styleArray.length; i++) {
// 'font-size:20px;color:red;',如此最后字符串有";"的话会导致styleArray最后一个元素为空字符串这里需要过滤
if (styleArray[i]) {
const item = styleArray[i].split(':')
style[trim(item[0])] = trim(item[1])
}
}
return style
}
// 这里为对象转字符串形式
let string = ''
for (const i in customStyle) {
// 驼峰转为中划线的形式否则css内联样式无法识别驼峰样式属性名
const key = i.replace(/([A-Z])/g, '-$1').toLowerCase()
string += `${key}:${customStyle[i]};`
}
// 去除两端空格
return trim(string)
}
/**
* @description 添加单位如果有rpxupx%px等单位结尾或者值为auto直接返回否则加上px单位结尾
* @param {string|number} value 需要添加单位的值
* @param {string} unit 添加的单位名 比如px
*/
function addUnit(value = 'auto', unit = uni?.$w?.config?.unit ? uni?.$w?.config?.unit : 'px') {
value = String(value)
// 用wuui内置验证规则中的number判断是否为数值
return number(value) ? `${value}${unit}` : value
}
/**
* @description 深度克隆
* @param {object} obj 需要深度克隆的对象
* @param cache 缓存
* @returns {*} 克隆后的对象或者原值(不是对象)
*/
function deepClone(obj, cache = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (cache.has(obj)) return cache.get(obj);
let clone;
if (obj instanceof Date) {
clone = new Date(obj.getTime());
} else if (obj instanceof RegExp) {
clone = new RegExp(obj);
} else if (obj instanceof Map) {
clone = new Map(Array.from(obj, ([key, value]) => [key, deepClone(value, cache)]));
} else if (obj instanceof Set) {
clone = new Set(Array.from(obj, value => deepClone(value, cache)));
} else if (Array.isArray(obj)) {
clone = obj.map(value => deepClone(value, cache));
} else if (Object.prototype.toString.call(obj) === '[object Object]') {
clone = Object.create(Object.getPrototypeOf(obj));
cache.set(obj, clone);
for (const [key, value] of Object.entries(obj)) {
clone[key] = deepClone(value, cache);
}
} else {
clone = Object.assign({}, obj);
}
cache.set(obj, clone);
return clone;
}
/**
* @description JS对象深度合并
* @param {object} target 需要拷贝的对象
* @param {object} source 拷贝的来源对象
* @returns {object|boolean} 深度合并后的对象或者false入参有不是对象
*/
function deepMerge(target = {}, source = {}) {
target = deepClone(target)
if (typeof target !== 'object' || target === null || typeof source !== 'object' || source === null) return target;
const merged = Array.isArray(target) ? target.slice() : Object.assign({}, target);
for (const prop in source) {
if (!source.hasOwnProperty(prop)) continue;
const sourceValue = source[prop];
const targetValue = merged[prop];
if (sourceValue instanceof Date) {
merged[prop] = new Date(sourceValue);
} else if (sourceValue instanceof RegExp) {
merged[prop] = new RegExp(sourceValue);
} else if (sourceValue instanceof Map) {
merged[prop] = new Map(sourceValue);
} else if (sourceValue instanceof Set) {
merged[prop] = new Set(sourceValue);
} else if (typeof sourceValue === 'object' && sourceValue !== null) {
merged[prop] = deepMerge(targetValue, sourceValue);
} else {
merged[prop] = sourceValue;
}
}
return merged;
}
/**
* @description error提示
* @param {*} err 错误内容
*/
function error(err) {
// 开发环境才提示,生产环境不会提示
if (process.env.NODE_ENV === 'development') {
console.error(`wuui提示${err}`)
}
}
/**
* @description 打乱数组
* @param {array} array 需要打乱的数组
* @returns {array} 打乱后的数组
*/
function randomArray(array = []) {
// 原理是sort排序,Math.random()产生0<= x < 1之间的数,会导致x-0.05大于或者小于0
return array.sort(() => Math.random() - 0.5)
}
// padStart 的 polyfill因为某些机型或情况还无法支持es7的padStart比如电脑版的微信小程序
// 所以这里做一个兼容polyfill的兼容处理
if (!String.prototype.padStart) {
// 为了方便表示这里 fillString 用了ES6 的默认参数,不影响理解
String.prototype.padStart = function(maxLength, fillString = ' ') {
if (Object.prototype.toString.call(fillString) !== '[object String]') {
throw new TypeError(
'fillString must be String'
)
}
const str = this
// 返回 String(str) 这里是为了使返回的值是字符串字面量,在控制台中更符合直觉
if (str.length >= maxLength) return String(str)
const fillLength = maxLength - str.length
let times = Math.ceil(fillLength / fillString.length)
while (times >>= 1) {
fillString += fillString
if (times === 1) {
fillString += fillString
}
}
return fillString.slice(0, fillLength) + str
}
}
/**
* @description 格式化时间
* @param {String|Number} dateTime 需要格式化的时间戳
* @param {String} fmt 格式化规则 yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合 默认yyyy-mm-dd
* @returns {string} 返回格式化后的字符串
*/
function timeFormat(dateTime = null, formatStr = 'yyyy-mm-dd') {
let date
// 若传入时间为假值,则取当前时间
if (!dateTime) {
date = new Date()
}
// 若为unix秒时间戳则转为毫秒时间戳逻辑有点奇怪但不敢改以保证历史兼容
else if (/^\d{10}$/.test(dateTime?.toString().trim())) {
date = new Date(dateTime * 1000)
}
// 若用户传入字符串格式时间戳new Date无法解析需做兼容
else if (typeof dateTime === 'string' && /^\d+$/.test(dateTime.trim())) {
date = new Date(Number(dateTime))
}
// 处理平台性差异在Safari/Webkit中new Date仅支持/作为分割符的字符串时间
// 处理 '2022-07-10 01:02:03',跳过 '2022-07-10T01:02:03'
else if (typeof dateTime === 'string' && dateTime.includes('-') && !dateTime.includes('T')) {
date = new Date(dateTime.replace(/-/g, '/'))
}
// 其他都认为符合 RFC 2822 规范
else {
date = new Date(dateTime)
}
const timeSource = {
'y': date.getFullYear().toString(), // 年
'm': (date.getMonth() + 1).toString().padStart(2, '0'), // 月
'd': date.getDate().toString().padStart(2, '0'), // 日
'h': date.getHours().toString().padStart(2, '0'), // 时
'M': date.getMinutes().toString().padStart(2, '0'), // 分
's': date.getSeconds().toString().padStart(2, '0') // 秒
// 有其他格式化字符需求可以继续添加,必须转化成字符串
}
for (const key in timeSource) {
const [ret] = new RegExp(`${key}+`).exec(formatStr) || []
if (ret) {
// 年可能只需展示两位
const beginIndex = key === 'y' && ret.length === 2 ? 2 : 0
formatStr = formatStr.replace(ret, timeSource[key].slice(beginIndex))
}
}
return formatStr
}
/**
* @description 时间戳转为多久之前
* @param {String|Number} timestamp 时间戳
* @param {String|Boolean} format
* 格式化规则如果为时间格式字符串,超出一定时间范围,返回固定的时间格式;
* 如果为布尔值false无论什么时间都返回多久以前的格式
* @returns {string} 转化后的内容
*/
function timeFrom(timestamp = null, format = 'yyyy-mm-dd') {
if (timestamp == null) timestamp = Number(new Date())
timestamp = parseInt(timestamp)
// 判断用户输入的时间戳是秒还是毫秒,一般前端js获取的时间戳是毫秒(13位),后端传过来的为秒(10位)
if (timestamp.toString().length == 10) timestamp *= 1000
let timer = (new Date()).getTime() - timestamp
timer = parseInt(timer / 1000)
// 如果小于5分钟,则返回"刚刚",其他以此类推
let tips = ''
switch (true) {
case timer < 300:
tips = '刚刚'
break
case timer >= 300 && timer < 3600:
tips = `${parseInt(timer / 60)}分钟前`
break
case timer >= 3600 && timer < 86400:
tips = `${parseInt(timer / 3600)}小时前`
break
case timer >= 86400 && timer < 2592000:
tips = `${parseInt(timer / 86400)}天前`
break
default:
// 如果format为false则无论什么时间戳都显示xx之前
if (format === false) {
if (timer >= 2592000 && timer < 365 * 86400) {
tips = `${parseInt(timer / (86400 * 30))}个月前`
} else {
tips = `${parseInt(timer / (86400 * 365))}年前`
}
} else {
tips = timeFormat(timestamp, format)
}
}
return tips
}
/**
* @description 去除空格
* @param String str 需要去除空格的字符串
* @param String pos both(左右)|left|right|all 默认both
*/
function trim(str, pos = 'both') {
str = String(str)
if (pos == 'both') {
return str.replace(/^\s+|\s+$/g, '')
}
if (pos == 'left') {
return str.replace(/^\s*/, '')
}
if (pos == 'right') {
return str.replace(/(\s*$)/g, '')
}
if (pos == 'all') {
return str.replace(/\s+/g, '')
}
return str
}
/**
* @description 对象转url参数
* @param {object} data,对象
* @param {Boolean} isPrefix,是否自动加上"?"
* @param {string} arrayFormat 规则 indices|brackets|repeat|comma
*/
function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') {
const prefix = isPrefix ? '?' : ''
const _result = []
if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets'
for (const key in data) {
const value = data[key]
// 去掉为空的参数
if (['', undefined, null].indexOf(value) >= 0) {
continue
}
// 如果值为数组,另行处理
if (value.constructor === Array) {
// e.g. {ids: [1, 2, 3]}
switch (arrayFormat) {
case 'indices':
// 结果: ids[0]=1&ids[1]=2&ids[2]=3
for (let i = 0; i < value.length; i++) {
_result.push(`${key}[${i}]=${value[i]}`)
}
break
case 'brackets':
// 结果: ids[]=1&ids[]=2&ids[]=3
value.forEach((_value) => {
_result.push(`${key}[]=${_value}`)
})
break
case 'repeat':
// 结果: ids=1&ids=2&ids=3
value.forEach((_value) => {
_result.push(`${key}=${_value}`)
})
break
case 'comma':
// 结果: ids=1,2,3
let commaStr = ''
value.forEach((_value) => {
commaStr += (commaStr ? ',' : '') + _value
})
_result.push(`${key}=${commaStr}`)
break
default:
value.forEach((_value) => {
_result.push(`${key}[]=${_value}`)
})
}
} else {
_result.push(`${key}=${value}`)
}
}
return _result.length ? prefix + _result.join('&') : ''
}
/**
* 显示消息提示框
* @param {String} title 提示的内容,长度与 icon 取值有关。
* @param {Number} duration 提示的延迟时间单位毫秒默认2000
*/
function toast(title, duration = 2000) {
uni.showToast({
title: String(title),
icon: 'none',
duration
})
}
/**
* @description 根据主题type值,获取对应的图标
* @param {String} type 主题名称,primary|info|error|warning|success
* @param {boolean} fill 是否使用fill填充实体的图标
*/
function type2icon(type = 'success', fill = false) {
// 如果非预置值,默认为success
if (['primary', 'info', 'error', 'warning', 'success'].indexOf(type) == -1) type = 'success'
let iconName = ''
// 目前(2019-12-12),info和primary使用同一个图标
switch (type) {
case 'primary':
iconName = 'info-circle'
break
case 'info':
iconName = 'info-circle'
break
case 'error':
iconName = 'close-circle'
break
case 'warning':
iconName = 'error-circle'
break
case 'success':
iconName = 'checkmark-circle'
break
default:
iconName = 'checkmark-circle'
}
// 是否是实体类型,加上-fill,在icon组件库中,实体的类名是后面加-fill的
if (fill) iconName += '-fill'
return iconName
}
/**
* @description 数字格式化
* @param {number|string} number 要格式化的数字
* @param {number} decimals 保留几位小数
* @param {string} decimalPoint 小数点符号
* @param {string} thousandsSeparator 千分位符号
* @returns {string} 格式化后的数字
*/
function priceFormat(number, decimals = 0, decimalPoint = '.', thousandsSeparator = ',') {
number = (`${number}`).replace(/[^0-9+-Ee.]/g, '')
const n = !isFinite(+number) ? 0 : +number
const prec = !isFinite(+decimals) ? 0 : Math.abs(decimals)
const sep = (typeof thousandsSeparator === 'undefined') ? ',' : thousandsSeparator
const dec = (typeof decimalPoint === 'undefined') ? '.' : decimalPoint
let s = ''
s = (prec ? round(n, prec) + '' : `${Math.round(n)}`).split('.')
const re = /(-?\d+)(\d{3})/
while (re.test(s[0])) {
s[0] = s[0].replace(re, `$1${sep}$2`)
}
if ((s[1] || '').length < prec) {
s[1] = s[1] || ''
s[1] += new Array(prec - s[1].length + 1).join('0')
}
return s.join(dec)
}
/**
* @description 获取duration值
* 如果带有ms或者s直接返回如果大于一定值认为是ms单位小于一定值认为是s单位
* 比如以30位阈值那么300大于30可以理解为用户想要的是300ms而不是想花300s去执行一个动画
* @param {String|number} value 比如: "1s"|"100ms"|1|100
* @param {boolean} unit 提示: 如果是false 默认返回number
* @return {string|number}
*/
function getDuration(value, unit = true) {
const valueNum = parseInt(value)
if (unit) {
if (/s$/.test(value)) return value
return value > 30 ? `${value}ms` : `${value}s`
}
if (/ms$/.test(value)) return valueNum
if (/s$/.test(value)) return valueNum > 30 ? valueNum : valueNum * 1000
return valueNum
}
/**
* @description 日期的月或日补零操作
* @param {String} value 需要补零的值
*/
function padZero(value) {
return `00${value}`.slice(-2)
}
/**
* @description 在wu-form的子组件内容发生变化或者失去焦点时尝试通知wu-form执行校验方法
* @param {*} instance
* @param {*} event
*/
function formValidate(instance, event) {
const formItem = $parent.call(instance, 'wu-form-item')
const form = $parent.call(instance, 'wu-form')
// 如果发生变化的input或者textarea等其父组件中有wu-form-item或者wu-form等就执行form的validate方法
// 同时将form-item的pros传递给form让其进行精确对象验证
if (formItem && form) {
form.validateField(formItem.prop, () => {}, event)
}
}
/**
* @description 获取某个对象下的属性,用于通过类似'a.b.c'的形式去获取一个对象的的属性的形式
* @param {object} obj 对象
* @param {string} key 需要获取的属性字段
* @returns {*}
*/
function getProperty(obj, key) {
if (!obj) {
return
}
if (typeof key !== 'string' || key === '') {
return ''
}
if (key.indexOf('.') !== -1) {
const keys = key.split('.')
let firstObj = obj[keys[0]] || {}
for (let i = 1; i < keys.length; i++) {
if (firstObj) {
firstObj = firstObj[keys[i]]
}
}
return firstObj
}
return obj[key]
}
/**
* @description 设置对象的属性值,如果'a.b.c'的形式进行设置
* @param {object} obj 对象
* @param {string} key 需要设置的属性
* @param {string} value 设置的值
*/
function setProperty(obj, key, value) {
if (!obj) {
return
}
// 递归赋值
const inFn = function(_obj, keys, v) {
// 最后一个属性key
if (keys.length === 1) {
_obj[keys[0]] = v
return
}
// 0~length-1个key
while (keys.length > 1) {
const k = keys[0]
if (!_obj[k] || (typeof _obj[k] !== 'object')) {
_obj[k] = {}
}
const key = keys.shift()
// 自调用判断是否存在属性,不存在则自动创建对象
inFn(_obj[k], keys, v)
}
}
if (typeof key !== 'string' || key === '') {
} else if (key.indexOf('.') !== -1) { // 支持多层级赋值操作
const keys = key.split('.')
inFn(obj, keys, value)
} else {
obj[key] = value
}
}
/**
* @description 获取当前页面路径
*/
function page() {
const pages = getCurrentPages();
const route = pages[pages.length - 1]?.route;
// 某些特殊情况下(比如页面进行redirectTo时的一些时机)pages可能为空数组
return `/${route ? route : ''}`
}
/**
* @description 获取当前路由栈实例数组
*/
function pages() {
const pages = getCurrentPages()
return pages
}
/**
* 获取页面历史栈指定层实例
* @param back {number} [0] - 0或者负数表示获取历史栈的哪一层0表示获取当前页面实例-1 表示获取上一个页面实例。默认0。
*/
function getHistoryPage(back = 0) {
const pages = getCurrentPages()
const len = pages.length
return pages[len - 1 + back]
}
/**
* @description 修改wuui内置属性值
* @param {object} props 修改内置props属性
* @param {object} config 修改内置config属性
* @param {object} color 修改内置color属性
* @param {object} zIndex 修改内置zIndex属性
*/
function setConfig({
props = {},
config = {},
color = {},
zIndex = {}
}) {
const {
deepMerge,
} = uni.$w
uni.$w.config = deepMerge(uni.$w.config, config)
uni.$w.props = deepMerge(uni.$w.props, props)
uni.$w.color = deepMerge(uni.$w.color, color)
uni.$w.zIndex = deepMerge(uni.$w.zIndex, zIndex)
}
export {
range,
getPx,
sleep,
os,
sys,
random,
guid,
$parent,
addStyle,
addUnit,
deepClone,
deepMerge,
error,
randomArray,
timeFormat,
timeFrom,
trim,
queryParams,
toast,
type2icon,
priceFormat,
getDuration,
padZero,
formValidate,
getProperty,
setProperty,
page,
pages,
getHistoryPage,
setConfig,
Color
}

View File

@@ -0,0 +1,75 @@
/**
* 注意:
* 此部分内容在vue-cli模式下需要在vue.config.js加入如下内容才有效
* module.exports = {
* transpileDependencies: ['uview-v2']
* }
*/
let platform = 'none'
// #ifdef VUE3
platform = 'vue3'
// #endif
// #ifdef VUE2
platform = 'vue2'
// #endif
// #ifdef APP-PLUS
platform = 'plus'
// #endif
// #ifdef APP-NVUE
platform = 'nvue'
// #endif
// #ifdef H5
platform = 'h5'
// #endif
// #ifdef MP-WEIXIN
platform = 'weixin'
// #endif
// #ifdef MP-ALIPAY
platform = 'alipay'
// #endif
// #ifdef MP-BAIDU
platform = 'baidu'
// #endif
// #ifdef MP-TOUTIAO
platform = 'toutiao'
// #endif
// #ifdef MP-QQ
platform = 'qq'
// #endif
// #ifdef MP-KUAISHOU
platform = 'kuaishou'
// #endif
// #ifdef MP-360
platform = '360'
// #endif
// #ifdef MP
platform = 'mp'
// #endif
// #ifdef QUICKAPP-WEBVIEW
platform = 'quickapp-webview'
// #endif
// #ifdef QUICKAPP-WEBVIEW-HUAWEI
platform = 'quickapp-webview-huawei'
// #endif
// #ifdef QUICKAPP-WEBVIEW-UNION
platform = 'quckapp-webview-union'
// #endif
export default platform

View File

@@ -0,0 +1,287 @@
/**
* 验证电子邮箱格式
*/
function email(value) {
return /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/.test(value)
}
/**
* 验证手机格式
*/
function mobile(value) {
return /^1([3589]\d|4[5-9]|6[1-2,4-7]|7[0-8])\d{8}$/.test(value)
}
/**
* 验证URL格式
*/
function url(value) {
return /^((https|http|ftp|rtsp|mms):\/\/)(([0-9a-zA-Z_!~*'().&=+$%-]+: )?[0-9a-zA-Z_!~*'().&=+$%-]+@)?(([0-9]{1,3}.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z].[a-zA-Z]{2,6})(:[0-9]{1,4})?((\/?)|(\/[0-9a-zA-Z_!~*'().;?:@&=+$,%#-]+)+\/?)$/
.test(value)
}
/**
* 验证日期格式
*/
function date(value) {
if (!value) return false
// 判断是否数值或者字符串数值(意味着为时间戳)转为数值否则new Date无法识别字符串时间戳
if (number(value)) value = +value
return !/Invalid|NaN/.test(new Date(value).toString())
}
/**
* 验证ISO类型的日期格式
*/
function dateISO(value) {
return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value)
}
/**
* 验证十进制数字
*/
function number(value) {
return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value)
}
/**
* 验证字符串
*/
function string(value) {
return typeof value === 'string'
}
/**
* 验证整数
*/
function digits(value) {
return /^\d+$/.test(value)
}
/**
* 验证身份证号码
*/
function idCard(value) {
return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(
value
)
}
/**
* 是否车牌号
*/
function carNo(value) {
// 新能源车牌
const xreg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/
// 旧车牌
const creg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/
if (value.length === 7) {
return creg.test(value)
} if (value.length === 8) {
return xreg.test(value)
}
return false
}
/**
* 金额,只允许2位小数
*/
function amount(value) {
// 金额,只允许保留两位小数
return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(value)
}
/**
* 中文
*/
function chinese(value) {
const reg = /^[\u4e00-\u9fa5]+$/gi
return reg.test(value)
}
/**
* 只能输入字母
*/
function letter(value) {
return /^[a-zA-Z]*$/.test(value)
}
/**
* 只能是字母或者数字
*/
function enOrNum(value) {
// 英文或者数字
const reg = /^[0-9a-zA-Z]*$/g
return reg.test(value)
}
/**
* 验证是否包含某个值
*/
function contains(value, param) {
return value.indexOf(param) >= 0
}
/**
* 验证一个值范围[min, max]
*/
function range(value, param) {
return value >= param[0] && value <= param[1]
}
/**
* 验证一个长度范围[min, max]
*/
function rangeLength(value, param) {
return value.length >= param[0] && value.length <= param[1]
}
/**
* 是否固定电话
*/
function landline(value) {
const reg = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/
return reg.test(value)
}
/**
* 判断是否为空
*/
function empty(value) {
switch (typeof value) {
case 'undefined':
return true
case 'string':
if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true
break
case 'boolean':
if (!value) return true
break
case 'number':
if (value === 0 || isNaN(value)) return true
break
case 'object':
if (value === null || value.length === 0) return true
for (const i in value) {
return false
}
return true
}
return false
}
/**
* 是否json字符串
*/
function jsonString(value) {
if (typeof value === 'string') {
try {
const obj = JSON.parse(value)
if (typeof obj === 'object' && obj) {
return true
}
return false
} catch (e) {
return false
}
}
return false
}
/**
* 是否数组
*/
function array(value) {
if (typeof Array.isArray === 'function') {
return Array.isArray(value)
}
return Object.prototype.toString.call(value) === '[object Array]'
}
/**
* 是否对象
*/
function object(value) {
return Object.prototype.toString.call(value) === '[object Object]'
}
/**
* 是否短信验证码
*/
function code(value, len = 6) {
return new RegExp(`^\\d{${len}}$`).test(value)
}
/**
* 是否函数方法
* @param {Object} value
*/
function func(value) {
return typeof value === 'function'
}
/**
* 是否promise对象
* @param {Object} value
*/
function promise(value) {
return object(value) && func(value.then) && func(value.catch)
}
/** 是否图片格式
* @param {Object} value
*/
function image(value) {
const newValue = value.split('?')[0]
const IMAGE_REGEXP = /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i
return IMAGE_REGEXP.test(newValue)
}
/**
* 是否视频格式
* @param {Object} value
*/
function video(value) {
const VIDEO_REGEXP = /\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i
return VIDEO_REGEXP.test(value)
}
/**
* 是否为正则对象
* @param {Object}
* @return {Boolean}
*/
function regExp(o) {
return o && Object.prototype.toString.call(o) === '[object RegExp]'
}
export {
email,
mobile,
url,
date,
dateISO,
number,
digits,
idCard,
carNo,
amount,
chinese,
letter,
enOrNum,
contains,
range,
rangeLength,
empty,
jsonString,
landline,
object,
array,
code,
func,
promise,
video,
image,
regExp,
string
}

View File

@@ -0,0 +1,30 @@
let timer; let
flag
/**
* 节流原理:在一定时间内,只能触发一次
*
* @param {Function} func 要执行的回调函数
* @param {Number} wait 延时的时间
* @param {Boolean} immediate 是否立即执行
* @return null
*/
function throttle(func, wait = 500, immediate = true) {
if (immediate) {
if (!flag) {
flag = true
// 如果是立即执行则在wait毫秒内开始时执行
typeof func === 'function' && func()
timer = setTimeout(() => {
flag = false
}, wait)
}
} else if (!flag) {
flag = true
// 如果是非立即执行则在wait毫秒内的结束处执行
timer = setTimeout(() => {
flag = false
typeof func === 'function' && func()
}, wait)
}
}
export default throttle