进制(三):hex/rgb/hsl 色值, 实现转换工具

自给自足。代码:https://laputaz.github.io/color-converter/

在线 Demo: https://laputaz.github.io/color-converter/

背景

在日常的 css 编写中,经常会和色值打交道,最常见的几种格式便是:

1
2
3
color: #12fe21;
color: rgb(123, 11, 56);
color: hsl(0deg 48% 33%);

这三种色值分别称为:hex 色值,rgb 色值,hsl 色值。

色值组成

那么这三种色值为什么是这么表示的呢?首先有一个“原色”的概念:

原色是指不能透过其他颜色的混合调配而得出的“基本色”。 –wiki

常见的说法是,三原色为:红、绿、蓝。但是这个说法不是固定的,“原色”并不是一个物理概念,而是一个生物概念,是指人的肉眼对于光线的生理作用。在显示,印刷,和绘画等领域,“三原色”定义都可能不一样。

而以上提到的三种色值表示法,使用的是三原色光模式 RGB (红、绿、蓝)。

hex

hex 也就是 hexadecimal,十六进制表示法。因为是 16 进制,所以每一位的取值范围都是 0 ~ f, 其中:
前 2 位代表红色的浓度,中间 2 位代表绿色浓度,最后 2 位代表蓝色浓度

#ff0000 => 纯红色
#00ff00 => 纯绿色
#0000ff => 纯蓝色

当每两位数值一样的时候,可以简写,如:
#f00 => 纯红色

一些浏览器支持 8 位,末尾两位为透明度:
#ff000088 => 纯红色半透明

按照这种关系,那就可以随意组合了,例如:
#ffff00 => 红色+绿色=黄色
#ff00ff => 红色+蓝色=品红
#00ffff88 => 绿色+蓝色+半透明=半透明靛青

rgb

rgb 表示法,跟 hex 类似,只是采用了 10 进制表示数字,前面提到的 hex 表示法,用两位 16 进制数表示一个颜色,所以转换成十进制的话,一共有 16*16=256 种组合,即范围是 0 ~ 255
rgb(255,0,0) => 纯红色
rgb(0,255,0) => 纯绿色
rgb(0,0,255) => 纯蓝色

同样,可以增加一位,表示透明度,这个时候就要写成 rgba 了,其中 a 表示 阿尔法通道(α Channel或Alpha Channel)是指一张图片的透明和半透明度。,取值是 0 ~ 1
rgba(0, 255, 255, 0.5) => 绿色+蓝色+半透明=半透明靛青

hsl

hsl 和 前面两种表示方式有一些区别,hsl 指的是 色相、饱和度、亮度(Hue, Saturation, Lightness)

色相(H)是色彩的基本属性,就是平常所说的颜色名称,如红色、黄色等。
饱和度(S)是指色彩的纯度,越高色彩越纯,低则逐渐变灰,取 0-100%的数值。
明度(V),亮度(L),取 0-100%。

表示方式:

hsl(0deg 100% 50%) => 纯红色
hsl(120deg 100% 50%) => 纯绿色
hsl(240deg 100% 50%) => 纯蓝色

同样,可以增加一位表示透明度, 改写成 hsla:

hsla(240deg, 100%, 50%, 50%) => 半透明纯蓝色

色值转换

这里主要说 hex 和 rgb 的转换,至于 hsl 比较复杂,它是将 rgb 使用的笛卡尔坐标系转换成用圆柱坐标系来表示,所以涉及到几何、向量等计算,早就忘光了。有兴趣可以看:

hex 转换成 rgb

也就是每两位 16 进制转换成 10 进制,比较简单,如 ef 转换成 10 进制是 15 * 16**0 + 14 * 16**1

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 错误信息
const WRONG_MESSAGE = 'wrong format.'
// 转换成 10 进制,柯里
const pInt = (num) => parseInt(num, 16)
// hex 转换成 rgb
const convertHexToRGB = (hex) => {
// 去掉 #
let str = hex.replace('#', '')
// 判断剩下的长度是不是 3 、6
if (![3, 6].includes(str.length)) return WRONG_MESSAGE
// 长度是 3,则拓展为 6 位
if (str.length === 3) {
str = `${str[0]}${str[0]}${str[1]}${str[1]}${str[2]}${str[2]}`
}
// 每 2 位转换为 10 进制 (第1位 * 16^0 + 第2位 * 16^1)
const R = pInt(str[1]) + pInt(str[0]) * 16
const G = pInt(str[3]) + pInt(str[2]) * 16
const B = pInt(str[5]) + pInt(str[4]) * 16
return `rgb(${R},${G},${B})`
}

// 使用
convertHexToRGB('#ff0000') // 输出 rgb(255,0,0)

rgb 转换成 hex

将 10 进制数转换成 2 位的 16 进制数,思路是:

  1. 转换后的事必须是 2 位,所以第 2 位必须是能被 16 整除的,否则不可能进位
  2. 第一位最大是 15 (f)

数字减去 15 后,仍能被 16 整除,以这个思路不断循环即可,例如 (254 - 15) / 16 = 15,那么结果就是 15 15 => ff

或者更简单的是,直接用 toString():

1
2
const num = 255
num.toString(16) // ’ff‘

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const WRONG_MESSAGE = ''
// 十进制转两位16进制
//! 或者 const getOctal = (num) => num.toString(16)
const getOctal = (num) => {
let count = 15
while (count > 0) {
let res = (num - count) / 16
if (!(res + '').includes('.')) {
return `${res.toString(16)}${count.toString(16)}`
}
count--
}
return 'ff'
}
// 转换
const convertRGBToHex = (rgb) => {
let arr = []
try {
arr = rgb.replace('rgb(', '').replace(')', '').split(',')
} catch (err) {
return WRONG_MESSAGE
}
if (arr.length < 3) {
return WRONG_MESSAGE
}
const R = getOctal(arr[0])
const G = getOctal(arr[1])
const B = getOctal(arr[2])
return `#${R}${G}${B}`
}

Demo

这里部署了一个在线的 Demo:

https://laputaz.github.io/color-converter/