颜色转换
<style>.pa { margin: 10px auto; width: 740px; height: 740px; font-size: 16px; position: relative; }
#msvg { position: absolute; width: 100%; height: 90%; }
#msvg circle { cursor: pointer; }
#msvg text { font-size: 10px; text-anchor: middle; dominant-baseline: middle; }
#msvg line { cursor: pointer; stroke: silver; stroke-width: 4; }
.pa h1 { margin: 0; padding: 20px; font-size: 2em; text-align: center; }
</style>
<div id="pa" class="pa">
<h1 class="txtCenter">颜色转换</h1>
<svg id="msvg" viewBox="-200 -200 400 400">
<marker id="arrow" viewBox="0 0 5 5" refX="3.5" refY="2.5" markerWidth="3" markerHeight="3" orient="auto">
<path d="M 0 0 L 5 2.5 L 0 5 z" fill="gray" />
</marker>
<circle id="cHex" cx="-120" cy="-100" r="75" fill="transparent" stroke="silver">
<title>十六进制 : 点击生成随机颜色</title>
</circle>
<circle id="cRgb" cx="120" cy="-100" r="75" fill="transparent" stroke="silver">
<title>RGB : 点击生成随机颜色</title>
</circle>
<circle id="cHsl" cx="0" cy="100" r="75" fill="transparent" stroke="silver">
<title>HSL : 点击生成随机颜色</title>
</circle>
<line id="lHex2Rgb" x1="-10" y1="-110" x2="10" y2="-110" marker-end="url(#arrow)">
<title>十六进制转RGB</title>
</line>
<line id="lRgb2Hex" x1="10" y1="-90" x2="-10" y2="-90" marker-end="url(#arrow)">
<title>RGB转十六进制</title>
</line>
<line id="lHex2Hsl" x1="-80" y1="-10" x2="-70" y2="10" marker-end="url(#arrow)">
<title>十六进制转HSL</title>
</line>
<line id="lHsl2Hex" x1="-50" y1="10" x2="-60" y2="-10" marker-end="url(#arrow)">
<title>HSL转十六进制</title>
</line>
<line id="lRgb2Hsl" x1="70" y1="-10" x2="60" y2="10" marker-end="url(#arrow)">
<title>RGB转HSL</title>
</line>
<line id="lHsl2Rgb" x1="40" y1="10" x2="50" y2="-10" marker-end="url(#arrow)">
<title>HSL转RGB</title>
</line>
<text id="tHex" x="-120" y="0">HEX</text>
<text id="tRgb" x="120" y="0">RGB</text>
<text id="tHsl" x="0" y="190">HSL</text>
</svg>
</div>
<script type="module">
const getRanHex = () => `#${Math.random().toString(16).substring(2,8)}`;
const getRanRgb = () => {
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
return `rgb(${r}, ${g}, ${b})`;
};
const getRanHsl = () => `hsl(${Math.floor(Math.random() * 361)}, 100%, 50%)`;
const HexColor2Rgb = (str) => {
const fullHex = (str) => {
let newStr = str.replace('#', '');
if (newStr.length === 3) {
return newStr.split('').map(i => i.repeat(2)).join('');
}
return newStr;
};
const Hex2Dec = (str) => parseInt(str, 16);
const ar = fullHex(str).match(/.{2}/g).map(a => Hex2Dec(a));
return 'rgb(' + ar.join(', ') + ')';
};
const RgbColor2Hex = (str) => {
const regex = /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(+))?\s*\)/i;
const matches = str.match(regex);
if (!matches) return null;
let r = Math.min(255, Math.max(0, parseInt(matches, 10)));
let g = Math.min(255, Math.max(0, parseInt(matches, 10)));
let b = Math.min(255, Math.max(0, parseInt(matches, 10)));
const toHex = (n) => n.toString(16).padStart(2, '0');
let hex = `#${toHex(r)}${toHex(g)}${toHex(b)}`;
return hex;
};
const RgbColor2Hsl = (str) => {
const rgb2Hsl = (r, g, b) => {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d /(max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return ;
};
const regex = /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(+))?\s*\)/i;
const matches = str.match(regex);
if (!matches) return null;
let r = Math.min(255, Math.max(0, parseInt(matches, 10)));
let g = Math.min(255, Math.max(0, parseInt(matches, 10)));
let b = Math.min(255, Math.max(0, parseInt(matches, 10)));
const colors = rgb2Hsl(r, g, b);
const h = Math.round(colors * 360);
const s = Math.round(colors * 100) + '%';
const l = Math.round(colors * 100) + '%';
return `hsl(${h}, ${s}, ${l})`;
};
const HslColor2Rgb = (str) => {
const hsl2Rgb = (h, s, l) => {
let r, g, b;
if(s === 0) {
r = g = b = l;
} else {
const hue2rgb = function hue2rgb(p, q, t) {
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return ;
};
const regex = /hsla?\((\d+),\s*([\d.]+)%,\s*([\d.]+)%(\s*,\s*[\d.]+)?\)/i;
const matches = str.match(regex);
if (!matches) return null;
const h = parseInt(matches, 10) / 360;
const s = parseFloat(matches) / 100;
const l = parseFloat(matches) / 100;
const colors = hsl2Rgb(h, s, l);
const r = colors;
const g = colors;
const b = colors;
return `rgb(${r}, ${g}, ${b})`;
};
cHex.onclick = () => {
const color = getRanHex();
cHex.setAttribute('fill', color);
tHex.textContent = color;
}
cRgb.onclick = () => {
const color = getRanRgb();
cRgb.setAttribute('fill', color);
tRgb.textContent = color;
};
cHsl.onclick = () => {
const color = getRanHsl();
cHsl.setAttribute('fill', color);
tHsl.textContent = color;
};
lHex2Rgb.onclick = () => {
const color = HexColor2Rgb(tHex.textContent);
cRgb.setAttribute('fill', color);
tRgb.textContent = color;
};
lRgb2Hex.onclick = () => {
const color = RgbColor2Hex(tRgb.textContent);
cHex.setAttribute('fill', color);
tHex.textContent = color;
};
lRgb2Hsl.onclick = () => {
const color = RgbColor2Hsl(tRgb.textContent)
cHsl.setAttribute('fill', color);
tHsl.textContent = color;
};
lHsl2Rgb.onclick = () => {
const color = HslColor2Rgb(tHsl.textContent);
cRgb.setAttribute('fill', color);
tRgb.textContent = color;
};
lHsl2Hex.onclick = () => {
const color = RgbColor2Hex(HslColor2Rgb(tHsl.textContent));
cHex.setAttribute('fill', color);
tHex.textContent = color;
};
lHex2Hsl.onclick = () => {
const color = RgbColor2Hsl(HexColor2Rgb(tHex.textContent));
cHsl.setAttribute('fill', color);
tHsl.textContent = color;
};
window.onload = () => {
const hexColor = getRanHex();
const rgbColor = getRanRgb();
const hslColor = getRanHsl();
cHex.setAttribute('fill', hexColor);
tHex.textContent = hexColor;
cRgb.setAttribute('fill', rgbColor);
tRgb.textContent = rgbColor;
cHsl.setAttribute('fill', hslColor);
tHsl.textContent = hslColor;
};
</script> 源码
<style>
.pa { margin: 10px auto; width: 740px; height: 740px; font-size: 16px; position: relative; }
#msvg { position: absolute; width: 100%; height: 90%; }
#msvg circle { cursor: pointer; }
#msvg text { font-size: 10px; text-anchor: middle; dominant-baseline: middle; }
#msvg line { cursor: pointer; stroke: silver; stroke-width: 4; }
.pa h1 { margin: 0; padding: 20px; font-size: 2em; text-align: center; }
</style>
<div id="pa" class="pa">
<h1 class="txtCenter">颜色转换</h1>
<svg id="msvg" viewBox="-200 -200 400 400">
<marker id="arrow" viewBox="0 0 5 5" refX="3.5" refY="2.5" markerWidth="3" markerHeight="3" orient="auto">
<path d="M 0 0 L 5 2.5 L 0 5 z" fill="gray" />
</marker>
<circle id="cHex" cx="-120" cy="-100" r="75" fill="transparent" stroke="silver">
<title>十六进制 : 点击生成随机颜色</title>
</circle>
<circle id="cRgb" cx="120" cy="-100" r="75" fill="transparent" stroke="silver">
<title>RGB : 点击生成随机颜色</title>
</circle>
<circle id="cHsl" cx="0" cy="100" r="75" fill="transparent" stroke="silver">
<title>HSL : 点击生成随机颜色</title>
</circle>
<line id="lHex2Rgb" x1="-10" y1="-110" x2="10" y2="-110" marker-end="url(#arrow)">
<title>十六进制转RGB</title>
</line>
<line id="lRgb2Hex" x1="10" y1="-90" x2="-10" y2="-90" marker-end="url(#arrow)">
<title>RGB转十六进制</title>
</line>
<line id="lHex2Hsl" x1="-80" y1="-10" x2="-70" y2="10" marker-end="url(#arrow)">
<title>十六进制转HSL</title>
</line>
<line id="lHsl2Hex" x1="-50" y1="10" x2="-60" y2="-10" marker-end="url(#arrow)">
<title>HSL转十六进制</title>
</line>
<line id="lRgb2Hsl" x1="70" y1="-10" x2="60" y2="10" marker-end="url(#arrow)">
<title>RGB转HSL</title>
</line>
<line id="lHsl2Rgb" x1="40" y1="10" x2="50" y2="-10" marker-end="url(#arrow)">
<title>HSL转RGB</title>
</line>
<text id="tHex" x="-120" y="0">HEX</text>
<text id="tRgb" x="120" y="0">RGB</text>
<text id="tHsl" x="0" y="190">HSL</text>
</svg>
</div>
<script type="module">
const getRanHex = () => `#${Math.random().toString(16).substring(2,8)}`;
const getRanRgb = () => {
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
return `rgb(${r}, ${g}, ${b})`;
};
const getRanHsl = () => `hsl(${Math.floor(Math.random() * 361)}, 100%, 50%)`;
const HexColor2Rgb = (str) => {
const fullHex = (str) => {
let newStr = str.replace('#', '');
if (newStr.length === 3) {
return newStr.split('').map(i => i.repeat(2)).join('');
}
return newStr;
};
const Hex2Dec = (str) => parseInt(str, 16);
const ar = fullHex(str).match(/.{2}/g).map(a => Hex2Dec(a));
return 'rgb(' + ar.join(', ') + ')';
};
const RgbColor2Hex = (str) => {
const regex = /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(+))?\s*\)/i;
const matches = str.match(regex);
if (!matches) return null;
let r = Math.min(255, Math.max(0, parseInt(matches, 10)));
let g = Math.min(255, Math.max(0, parseInt(matches, 10)));
let b = Math.min(255, Math.max(0, parseInt(matches, 10)));
const toHex = (n) => n.toString(16).padStart(2, '0');
let hex = `#${toHex(r)}${toHex(g)}${toHex(b)}`;
return hex;
};
const RgbColor2Hsl = (str) => {
const rgb2Hsl = (r, g, b) => {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d /(max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return ;
};
const regex = /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(+))?\s*\)/i;
const matches = str.match(regex);
if (!matches) return null;
let r = Math.min(255, Math.max(0, parseInt(matches, 10)));
let g = Math.min(255, Math.max(0, parseInt(matches, 10)));
let b = Math.min(255, Math.max(0, parseInt(matches, 10)));
const colors = rgb2Hsl(r, g, b);
const h = Math.round(colors * 360);
const s = Math.round(colors * 100) + '%';
const l = Math.round(colors * 100) + '%';
return `hsl(${h}, ${s}, ${l})`;
};
const HslColor2Rgb = (str) => {
const hsl2Rgb = (h, s, l) => {
let r, g, b;
if(s === 0) {
r = g = b = l;
} else {
const hue2rgb = function hue2rgb(p, q, t) {
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return ;
};
const regex = /hsla?\((\d+),\s*([\d.]+)%,\s*([\d.]+)%(\s*,\s*[\d.]+)?\)/i;
const matches = str.match(regex);
if (!matches) return null;
const h = parseInt(matches, 10) / 360;
const s = parseFloat(matches) / 100;
const l = parseFloat(matches) / 100;
const colors = hsl2Rgb(h, s, l);
const r = colors;
const g = colors;
const b = colors;
return `rgb(${r}, ${g}, ${b})`;
};
cHex.onclick = () => {
const color = getRanHex();
cHex.setAttribute('fill', color);
tHex.textContent = color;
}
cRgb.onclick = () => {
const color = getRanRgb();
cRgb.setAttribute('fill', color);
tRgb.textContent = color;
};
cHsl.onclick = () => {
const color = getRanHsl();
cHsl.setAttribute('fill', color);
tHsl.textContent = color;
};
lHex2Rgb.onclick = () => {
const color = HexColor2Rgb(tHex.textContent);
cRgb.setAttribute('fill', color);
tRgb.textContent = color;
};
lRgb2Hex.onclick = () => {
const color = RgbColor2Hex(tRgb.textContent);
cHex.setAttribute('fill', color);
tHex.textContent = color;
};
lRgb2Hsl.onclick = () => {
const color = RgbColor2Hsl(tRgb.textContent)
cHsl.setAttribute('fill', color);
tHsl.textContent = color;
};
lHsl2Rgb.onclick = () => {
const color = HslColor2Rgb(tHsl.textContent);
cRgb.setAttribute('fill', color);
tRgb.textContent = color;
};
lHsl2Hex.onclick = () => {
const color = RgbColor2Hex(HslColor2Rgb(tHsl.textContent));
cHex.setAttribute('fill', color);
tHex.textContent = color;
};
lHex2Hsl.onclick = () => {
const color = RgbColor2Hsl(HexColor2Rgb(tHex.textContent));
cHsl.setAttribute('fill', color);
tHsl.textContent = color;
};
window.onload = () => {
const hexColor = getRanHex();
const rgbColor = getRanRgb();
const hslColor = getRanHsl();
cHex.setAttribute('fill', hexColor);
tHex.textContent = hexColor;
cRgb.setAttribute('fill', rgbColor);
tRgb.textContent = rgbColor;
cHsl.setAttribute('fill', hslColor);
tHsl.textContent = hslColor;
};
</script>
.pa 选择器规范了 svg 的尺寸,所以本演示具备自适应高宽能力,只需设置好 .pa 选择器的 width 和 height 属性值。
颜色的转换难度最大的是 hsl,由于颜色空间的体系不同,转换时会涉及到浮点数和三角函数运算,由于JS处理浮点数的天生缺陷,rgb - hsl 之间的往返转换会导致浮点数精准度的流失,特别是 hsl 转为 rgb 时表现明显,不过颜色效果人眼无法识别。
本演示颜色转换的核心算法,有一部分使用或参考了开源的代码,这是允许的也是合理的。特此声明。 另外:
一、演示代码已经支持对 hsl 的饱和度、亮度的支持,处于简化,演示固定了正常饱和度为 100%,亮度为 50%,s 和 l 的变化未参与到演示中。
二、演示未处理颜色 alpha 通道,但所使用的匹配颜色表达式德尔正则均一纳入 alpha通道,需要时可以随时启用。 魔术一样{:4_170:} 马黑黑 发表于 2025-11-20 19:45
.pa 选择器规范了 svg 的尺寸,所以本演示具备自适应高宽能力,只需设置好 .pa 选择器的 width 和 height...
惊叹,代码的魔力{:4_170:} 马黑黑 发表于 2025-11-20 19:50
另外:
一、演示代码已经支持对 hsl 的饱和度、亮度的支持,处于简化,演示固定了正常饱和度为 100%,亮 ...
先睡觉去了,明天抽时间再上来 三种颜色可以相互转换,这个厉害了{:4_199:} 马黑黑 发表于 2025-11-20 19:50
另外:
一、演示代码已经支持对 hsl 的饱和度、亮度的支持,处于简化,演示固定了正常饱和度为 100%,亮 ...
固定了饱和度和亮度也很不易了,否则需要考虑的更多了{:4_187:} 红影 发表于 2025-11-21 00:01
固定了饱和度和亮度也很不易了,否则需要考虑的更多了
算法都已经集成了,这已经不是问题。
主要演示页面的设计没有提供改变饱和度和亮度,使用的是正常情况下的颜色呈现,所以固定s和l分量 红影 发表于 2025-11-20 23:53
三种颜色可以相互转换,这个厉害了
这是现在Web页最常用到的三种颜色表示法。Hex和RGB同属于一个颜色体系,二者的颜色转换仅需要在进制中完成便可;hsl是另外的颜色空转,和前二者间的换有些难度,需要掌握的原理很多很深(维基百科上有详细介绍换算方法),但在实际应用中它更好控制,也能产生出更多更细致的颜色,很多场景需要用到它。 小辣椒 发表于 2025-11-20 23:07
先睡觉去了,明天抽时间再上来
88 小辣椒 发表于 2025-11-20 23:06
惊叹,代码的魔力
{:4_172:} 小辣椒 发表于 2025-11-20 23:05
魔术一样
{:4_190:}
页:
[1]