├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── babel.config.json ├── package-lock.json ├── package.json └── src ├── __tests__ └── test.mjs └── index.mjs /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | node: true, 6 | }, 7 | extends: [ 8 | 'airbnb-base', 9 | ], 10 | parserOptions: { 11 | ecmaVersion: 12, 12 | sourceType: 'module', 13 | }, 14 | rules: { 15 | 'import/extensions': ['error', { 16 | js: 'always', 17 | mjs: 'always', 18 | }], 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | lib/ 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | 3 | node_modules/ 4 | 5 | .travis.yml 6 | .eslintrc.js 7 | babel.config.json 8 | gulpfile.js 9 | src/ 10 | lib/__tests__/ 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | - 14 5 | - 13 6 | - 12 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2.0.0 - 2020-09-24 2 | * Rewrite as ES module. Some breaking changes to the API surface has been made as a result. 3 | * Better adhere to CSS specification (#4). 4 | * Add support for CSS Color Module Level 4 syntax. 5 | * Update dependencies. 6 | * Add eslint. 7 | * Add babel. 8 | 9 | # 1.1.1 - 2020-03-20 10 | * Fix alpha 0 incorrectly defaulting to 1 (#2). Thanks to jedwards1211. 11 | 12 | # 1.1.0 - 2016-08-13 13 | * Add `toHslaArray` method. 14 | 15 | # 1.0.2 - 2015-05-27 16 | * Fix typo causing `rgba()` to return incorrect results. 17 | 18 | # 1.0.1 - 2015-05-27 19 | * Fix `toHexString()` not padding values. 20 | 21 | # 1.0.0 - 2015-05-27 22 | * Initial release. 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2015 Andy Jansson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # css-color-converter [![Build Status][ci-img]][ci] 2 | 3 | Converts CSS colors from one representation to another 4 | 5 | [ci-img]: https://travis-ci.org/andyjansson/css-color-converter.svg 6 | [ci]: https://travis-ci.org/andyjansson/css-color-converter 7 | 8 | ## Installation 9 | 10 | ```js 11 | npm install css-color-converter 12 | ``` 13 | 14 | ## Usage 15 | 16 | **Example usage**: 17 | ```js 18 | import { fromString } from 'css-color-converter'; 19 | 20 | fromString('rgb(255, 255, 255)').toHslString(); // hsl(0, 0%, 100%) 21 | fromString('rgba(255, 255, 255, 0.5)').toHslString(); // hsla(0, 0%, 100%, 0.5) 22 | fromString('blue').toRgbString(); // rgb(0, 0, 255) 23 | fromString('red').toHexString(); // #ff0000 24 | ``` 25 | ## Functions 26 | 27 | ### `fromString(str)` 28 | | parameter | type | description | 29 | | --------- | ------ | ---------------------------------------------- | 30 | | `str` | string | Supports named colors, hex, rgb/rgba, hsl/hsla | 31 | 32 | **Returns** [`instance`](#Methods) if valid, `null` if invalid. 33 | 34 | ### `fromRgb([r, g, b])` 35 | 36 | | parameter | type | description | 37 | | --------- | ------ | ------------- | 38 | | `r` | int | red (0-255) | 39 | | `g` | int | green (0-255) | 40 | | `b` | int | blue (0-255) | 41 | 42 | **Returns** [`instance`](#Methods) 43 | 44 | ### `fromRgba([r, g, b, a])` 45 | 46 | | parameter | type | description | 47 | | --------- | ------ | ------------- | 48 | | `r` | int | red (0-255) | 49 | | `g` | int | green (0-255) | 50 | | `b` | int | blue (0-255) | 51 | | `a` | float | alpha (0-1) | 52 | 53 | **Returns** [`instance`](#Methods) 54 | 55 | ### `fromHsl([h, s, l])` 56 | 57 | | parameter | type | description | 58 | | --------- | ------ | ------------------ | 59 | | `h` | int | hue (0-360) | 60 | | `s` | int | saturation (0-100) | 61 | | `l` | int | luminosity (0-100) | 62 | 63 | **Returns** [`instance`](#Methods) 64 | 65 | ### `fromHsla([h, s, l, a])` 66 | 67 | | parameter | type | description | 68 | | --------- | ------ | ------------------ | 69 | | `h` | int | hue (0-360) | 70 | | `s` | int | saturation (0-100) | 71 | | `l` | int | luminosity (0-100) | 72 | | `a` | float | alpha (0-1) | 73 | 74 | **Returns** [`instance`](#Methods) 75 | 76 | ## Methods 77 | 78 | ### `toRgbString()` 79 | 80 | **Returns** `rgb()` or `rgba()`, depending on the alpha. 81 | 82 | ### `toHslString()` 83 | 84 | **Returns** `hsl()` or `hsla()`, depending on the alpha. 85 | 86 | ### `toHexString()` 87 | 88 | **Returns** 6-digit or 8-digit `hex`, depending on the alpha. 89 | 90 | ### `toRgbaArray()` 91 | 92 | **Returns** `[r, g, b, a]` array. -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-color-converter", 3 | "version": "2.0.0", 4 | "description": "Converts CSS colors from one representation to another", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "prepublish": "npm run lint && npm run build", 8 | "lint": "eslint --ext .mjs src", 9 | "build": "babel src -d lib", 10 | "pretest": "npm run lint", 11 | "test": "node src/__tests__/test.mjs" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/andyjansson/css-color-converter.git" 16 | }, 17 | "keywords": [ 18 | "css", 19 | "color", 20 | "conversions", 21 | "converter" 22 | ], 23 | "author": "Andy Jansson", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/andyjansson/css-color-converter/issues" 27 | }, 28 | "homepage": "https://github.com/andyjansson/css-color-converter", 29 | "dependencies": { 30 | "color-name": "^1.1.4", 31 | "css-unit-converter": "^1.1.2" 32 | }, 33 | "devDependencies": { 34 | "@babel/cli": "^7.15.7", 35 | "@babel/core": "^7.15.5", 36 | "@babel/preset-env": "^7.15.6", 37 | "eslint": "^7.32.0", 38 | "eslint-config-airbnb-base": "^14.2.1", 39 | "eslint-plugin-import": "^2.24.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/__tests__/test.mjs: -------------------------------------------------------------------------------- 1 | import { deepStrictEqual, strictEqual } from 'assert'; 2 | import { 3 | fromString, fromRgb, fromRgba, fromHsl, fromHsla, 4 | } from '../index.mjs'; 5 | 6 | deepStrictEqual(fromString('blue').toRgbaArray(), [0, 0, 255, 1]); 7 | deepStrictEqual(fromString('blue').toHslaArray(), [240, 100, 50, 1]); 8 | deepStrictEqual(fromString('blue').toHexString(), '#0000ff'); 9 | deepStrictEqual(fromString('rgb(255, 255, 255)').toRgbaArray(), [255, 255, 255, 1]); 10 | deepStrictEqual(fromString('rgb(100%, 100%, 100%)').toRgbaArray(), [255, 255, 255, 1]); 11 | deepStrictEqual(fromString('rgb(100%, 100%, 100%)').toHslaArray(), [0, 0, 100, 1]); 12 | deepStrictEqual(fromString('rgba(100%, 100%, 100%, .5)').toHexString(), '#ffffff7f'); 13 | deepStrictEqual(fromString('rgba(255, 255, 255, .5)').toRgbaArray(), [255, 255, 255, 0.5]); 14 | deepStrictEqual(fromString('rgba(255, 255, 255, .5)').toHslaArray(), [0, 0, 100, 0.5]); 15 | deepStrictEqual(fromString('rgb(255 255 255)').toRgbaArray(), [255, 255, 255, 1]); 16 | deepStrictEqual(fromString('rgb(100% 100% 100%)').toRgbaArray(), [255, 255, 255, 1]); 17 | deepStrictEqual(fromString('rgb(100% 100% 100%)').toHslaArray(), [0, 0, 100, 1]); 18 | deepStrictEqual(fromString('rgba(100% 100% 100% / .5)').toHexString(), '#ffffff7f'); 19 | deepStrictEqual(fromString('rgba(255 255 255 / .5)').toRgbaArray(), [255, 255, 255, 0.5]); 20 | deepStrictEqual(fromString('rgba(255 255 255 / .5)').toHslaArray(), [0, 0, 100, 0.5]); 21 | deepStrictEqual(fromString('rgba(0, 0, 0, 0)').toRgbaArray(), [0, 0, 0, 0]); 22 | deepStrictEqual(fromString('rgba(3, 2, 1, 0)').toRgbaArray(), [3, 2, 1, 0]); 23 | deepStrictEqual(fromString('hsl(0, 0%, 100%)').toHslaArray(), [0, 0, 100, 1]); 24 | deepStrictEqual(fromString('hsl(0, 0%, 100%)').toRgbaArray(), [255, 255, 255, 1]); 25 | deepStrictEqual(fromString('hsla(0, 0%, 100%, .5)').toHslaArray(), [0, 0, 100, 0.5]); 26 | deepStrictEqual(fromString('hsla(0, 0%, 100%, .5)').toRgbaArray(), [255, 255, 255, 0.5]); 27 | deepStrictEqual(fromString('#ffffff').toRgbaArray(), [255, 255, 255, 1]); 28 | deepStrictEqual(fromString('#ffffff').toHslaArray(), [0, 0, 100, 1]); 29 | deepStrictEqual(fromString('#ffffffff').toRgbaArray(), [255, 255, 255, 1]); 30 | deepStrictEqual(fromString('#ffffffff').toHslaArray(), [0, 0, 100, 1]); 31 | deepStrictEqual(fromString('#fff').toRgbaArray(), [255, 255, 255, 1]); 32 | deepStrictEqual(fromString('#fff').toHslaArray(), [0, 0, 100, 1]); 33 | deepStrictEqual(fromString('#ffff').toRgbaArray(), [255, 255, 255, 1]); 34 | deepStrictEqual(fromString('#ffff').toHslaArray(), [0, 0, 100, 1]); 35 | deepStrictEqual(fromString('#ffffff7f').toRgbaArray(), [255, 255, 255, 0.5]); 36 | deepStrictEqual(fromString('#ffffff7f').toHslaArray(), [0, 0, 100, 0.5]); 37 | deepStrictEqual(fromString('#ffffff').toHexString(), '#ffffff'); 38 | strictEqual(fromString('#ffffff').toRgbString(), 'rgb(255, 255, 255)'); 39 | strictEqual(fromString('#ffffff7f').toRgbString(), 'rgba(255, 255, 255, 0.5)'); 40 | strictEqual(fromString('#ffffff00').toRgbString(), 'rgba(255, 255, 255, 0)'); 41 | strictEqual(fromString('#fff0').toRgbString(), 'rgba(255, 255, 255, 0)'); 42 | strictEqual(fromString('#ffffff').toHslString(), 'hsl(0, 0%, 100%)'); 43 | strictEqual(fromString('#ffffff7f').toHslString(), 'hsla(0, 0%, 100%, 0.5)'); 44 | strictEqual(fromString('#ffffff').toHexString(), '#ffffff'); 45 | strictEqual(fromString('#ffffff7f').toHexString(), '#ffffff7f'); 46 | strictEqual(fromRgb([255, 255, 255]).toRgbString(), 'rgb(255, 255, 255)'); 47 | strictEqual(fromRgba([255, 255, 255, 1]).toRgbString(), 'rgb(255, 255, 255)'); 48 | strictEqual(fromRgba([255, 255, 255, 0.5]).toRgbString(), 'rgba(255, 255, 255, 0.5)'); 49 | strictEqual(fromRgba([255, 255, 255, 1]).toHslString(), 'hsl(0, 0%, 100%)'); 50 | strictEqual(fromRgba([255, 255, 255, 0.5]).toHslString(), 'hsla(0, 0%, 100%, 0.5)'); 51 | strictEqual(fromRgba([255, 255, 255, 1]).toHexString(), '#ffffff'); 52 | strictEqual(fromRgba([255, 255, 255, 0.5]).toHexString(), '#ffffff7f'); 53 | deepStrictEqual(fromRgba([0, 0, 0, 0]).toRgbaArray(), [0, 0, 0, 0]); 54 | deepStrictEqual(fromRgba([3, 2, 1, 0]).toRgbaArray(), [3, 2, 1, 0]); 55 | strictEqual(fromHsl([0, 0, 100]).toHslString(), 'hsl(0, 0%, 100%)'); 56 | strictEqual(fromHsla([0, 0, 100, 1]).toRgbString(), 'rgb(255, 255, 255)'); 57 | strictEqual(fromHsla([0, 0, 100, 0.5]).toRgbString(), 'rgba(255, 255, 255, 0.5)'); 58 | strictEqual(fromHsla([0, 0, 100, 1]).toHslString(), 'hsl(0, 0%, 100%)'); 59 | strictEqual(fromHsla([0, 0, 100, 0.5]).toHslString(), 'hsla(0, 0%, 100%, 0.5)'); 60 | strictEqual(fromHsla([0, 0, 100, 1]).toHexString(), '#ffffff'); 61 | strictEqual(fromHsla([0, 0, 100, 0.5]).toHexString(), '#ffffff7f'); 62 | strictEqual(fromString('nonsense'), null); 63 | -------------------------------------------------------------------------------- /src/index.mjs: -------------------------------------------------------------------------------- 1 | import colorNames from 'color-name'; 2 | import unitConverter from 'css-unit-converter'; 3 | 4 | const hex = /^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})?$/; 5 | const shortHex = /^#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])?$/; 6 | const rgb = /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*(0|1|0?\.\d+|\d+%))?\s*\)$/; 7 | const rgbfn = /^rgba?\(\s*(\d+)\s+(\d+)\s+(\d+)(?:\s*\/\s*(0|1|0?\.\d+|\d+%))?\s*\)$/; 8 | const rgbperc = /^rgba?\(\s*(\d+%)\s*,\s*(\d+%)\s*,\s*(\d+%)(?:\s*,\s*(0|1|0?\.\d+|\d+%))?\s*\)$/; 9 | const rgbpercfn = /^rgba?\(\s*(\d+%)\s+(\d+%)\s+(\d+%)(?:\s*\/\s*(0|1|0?\.\d+|\d+%))?\s*\)$/; 10 | const hsl = /^hsla?\(\s*(\d+)(deg|rad|grad|turn)?\s*,\s*(\d+)%\s*,\s*(\d+)%(?:\s*,\s*(0|1|0?\.\d+|\d+%))?\s*\)$/; 11 | 12 | function contains(haystack, needle) { 13 | return haystack.indexOf(needle) > -1; 14 | } 15 | 16 | function rgbToHsl(r, g, b) { 17 | const rprim = r / 255; 18 | const gprim = g / 255; 19 | const bprim = b / 255; 20 | const cmax = Math.max(rprim, gprim, bprim); 21 | const cmin = Math.min(rprim, gprim, bprim); 22 | const delta = cmax - cmin; 23 | const l = (cmax + cmin) / 2; 24 | 25 | if (delta === 0) { 26 | return [0, 0, l * 100]; 27 | } 28 | const s = delta / (1 - Math.abs(2 * l - 1)); 29 | const h = (() => { 30 | switch (cmax) { 31 | case rprim: { 32 | return ((gprim - bprim) / delta) % 6; 33 | } 34 | case gprim: { 35 | return (bprim - rprim) / delta + 2; 36 | } 37 | default: { 38 | return (rprim - gprim) / delta + 4; 39 | } 40 | } 41 | })(); 42 | 43 | return [ 44 | h * 60, 45 | s * 100, 46 | l * 100, 47 | ]; 48 | } 49 | 50 | function hslToRgb(h, s, l) { 51 | const hprim = h / 60; 52 | const sprim = s / 100; 53 | const lprim = l / 100; 54 | const c = (1 - Math.abs(2 * lprim - 1)) * sprim; 55 | const x = c * (1 - Math.abs((hprim % 2) - 1)); 56 | const m = lprim - c / 2; 57 | const [rprim, gprim, bprim] = (() => { 58 | if (hprim < 1) return [c, x, 0]; 59 | if (hprim < 2) return [x, c, 0]; 60 | if (hprim < 3) return [0, c, x]; 61 | if (hprim < 4) return [0, x, c]; 62 | if (hprim < 5) return [x, 0, c]; 63 | return [c, 0, x]; 64 | })(); 65 | 66 | return [ 67 | (rprim + m) * 255, 68 | (gprim + m) * 255, 69 | (bprim + m) * 255, 70 | ]; 71 | } 72 | 73 | class Color { 74 | constructor([r, g, b, a]) { 75 | this.values = [ 76 | Math.max(Math.min(parseInt(r, 10), 255), 0), 77 | Math.max(Math.min(parseInt(g, 10), 255), 0), 78 | Math.max(Math.min(parseInt(b, 10), 255), 0), 79 | a == null ? 1 : Math.max(Math.min(parseFloat(a), 255), 0), 80 | ]; 81 | } 82 | 83 | toRgbString() { 84 | const [r, g, b, a] = this.values; 85 | if (a === 1) { 86 | return `rgb(${r}, ${g}, ${b})`; 87 | } 88 | return `rgba(${r}, ${g}, ${b}, ${a})`; 89 | } 90 | 91 | toHslString() { 92 | const [h, s, l, a] = this.toHslaArray(); 93 | if (a === 1) { 94 | return `hsl(${h}, ${s}%, ${l}%)`; 95 | } 96 | return `hsla(${h}, ${s}%, ${l}%, ${a})`; 97 | } 98 | 99 | toHexString() { 100 | let [r, g, b, a] = this.values; 101 | r = Number(r).toString(16).padStart(2, '0'); 102 | g = Number(g).toString(16).padStart(2, '0'); 103 | b = Number(b).toString(16).padStart(2, '0'); 104 | a = a < 1 ? parseInt(a * 255, 10).toString(16).padStart(2, '0') : ''; 105 | return `#${r}${g}${b}${a}`; 106 | } 107 | 108 | toRgbaArray() { 109 | return this.values; 110 | } 111 | 112 | toHslaArray() { 113 | const [r, g, b, a] = this.values; 114 | const [h, s, l] = rgbToHsl(r, g, b); 115 | return [h, s, l, a]; 116 | } 117 | } 118 | 119 | export function fromRgba([r, g, b, a]) { 120 | return new Color([r, g, b, a]); 121 | } 122 | 123 | export function fromRgb([r, g, b]) { 124 | return fromRgba([r, g, b, 1]); 125 | } 126 | 127 | export function fromHsla([h, s, l, a]) { 128 | const [r, g, b] = hslToRgb(h, s, l); 129 | return fromRgba([r, g, b, a]); 130 | } 131 | 132 | export function fromHsl([h, s, l]) { 133 | return fromHsla([h, s, l, 1]); 134 | } 135 | 136 | function fromHexString(str) { 137 | let [, r, g, b, a] = hex.exec(str) || shortHex.exec(str); 138 | r = parseInt(r.length < 2 ? r.repeat(2) : r, 16); 139 | g = parseInt(g.length < 2 ? g.repeat(2) : g, 16); 140 | b = parseInt(b.length < 2 ? b.repeat(2) : b, 16); 141 | a = (a && (parseInt(a.length < 2 ? a.repeat(2) : a, 16) / 255).toPrecision(1)) || 1; 142 | return fromRgba([r, g, b, a]); 143 | } 144 | 145 | function fromRgbString(str) { 146 | let [, r, g, b, a] = rgb.exec(str) || rgbperc.exec(str) || rgbfn.exec(str) || rgbpercfn.exec(str); 147 | r = contains(r, '%') ? (parseInt(r, 10) * 255) / 100 : parseInt(r, 10); 148 | g = contains(g, '%') ? (parseInt(g, 10) * 255) / 100 : parseInt(g, 10); 149 | b = contains(b, '%') > 0 ? (parseInt(b, 10) * 255) / 100 : parseInt(b, 10); 150 | a = a === undefined ? 1 : parseFloat(a) / (contains(a, '%') ? 100 : 1); 151 | return fromRgba([r, g, b, a]); 152 | } 153 | 154 | function fromHslString(str) { 155 | let [, h, unit, s, l, a] = hsl.exec(str); 156 | unit = unit || 'deg'; 157 | h = unitConverter(parseFloat(h), unit, 'deg'); 158 | s = parseFloat(s); 159 | l = parseFloat(l); 160 | a = a === undefined ? 1 : parseFloat(a) / (contains(a, '%') ? 100 : 1); 161 | return fromHsla([h, s, l, a]); 162 | } 163 | 164 | export function fromString(str) { 165 | if (colorNames[str]) { 166 | return fromRgb(colorNames[str]); 167 | } 168 | 169 | if (hex.test(str) || shortHex.test(str)) { 170 | return fromHexString(str); 171 | } 172 | 173 | if (rgb.test(str) || rgbperc.test(str) || rgbfn.test(str) || rgbpercfn.test(str)) { 174 | return fromRgbString(str); 175 | } 176 | 177 | if (hsl.test(str)) { 178 | return fromHslString(str); 179 | } 180 | 181 | return null; 182 | } 183 | 184 | export default { 185 | fromString, 186 | fromRgb, 187 | fromRgba, 188 | fromHsl, 189 | fromHsla, 190 | }; 191 | --------------------------------------------------------------------------------