本文详细解析如何使用原生 JavaScript 从零构建一个颜色选择器组件。文章首先对比了浏览器原生 input color 的兼容性问题,随后深入讲解 RGB、HSV 和 HSL 颜色模型的区别与转换原理。通过 CSS 线性渐变与 DOM 事件监听,实现了色相柱、饱和度/明度面板及透明度条的交互逻辑。最后探讨了基于 Canvas 像素提取颜色的优化方案,提供了一套完整的前端颜色处理解决方案。
在前端界面开发过程中,经常遇到需要动态调整颜色的场景,此时引入颜色选择器组件是常见的解决方案。
针对这一需求,最直观的思路是利用 HTML5 提供的 input color 元素。然而,实际应用中存在兼容性差异:Internet Explorer 不支持该特性,且 Chrome 与 Firefox 的渲染机制也不尽相同,具体表现可参考下图对比。

左侧展示了 Chrome 浏览器内置的颜色选择器控件,右侧则是 Firefox 调用的操作系统原生颜色选择器界面。
鉴于不同浏览器间的实现差异,开发一个统一且风格一致的颜色选择器组件显得尤为必要。本文将以 Chrome 内置选择器的样式为参考,演示如何实现一个通用的颜色选择器。
开发该组件的前提是掌握颜色模型的基本原理。
颜色模型
在前端开发中,最常用的颜色模型是 RGB(红绿蓝三基色模型)。目前浏览器支持的颜色显示模型仅有两种:RGB 和 HSL。
RGB 模型通过 0-255 范围内的红、绿、蓝数值混合生成颜色,这种表示方式对计算机友好,但不便于人类直观地进行颜色选取。因此,大多数颜色选择器采用更贴近视觉感知的 HSV 和 HSL 模型。关于颜色模型的详细理论,可参考颜色模型的基础知识。
值得注意的是,Chrome 浏览器内置的颜色选择器正是基于 HSV 模型构建的。
HSV 与 HSL 的区别
HSV 模型由色相(Hue)、饱和度(Saturation)和明度(Value)组成;而 HSL 模型则由色相(Hue)、饱和度(Saturation)和亮度(Lightness)构成。
其中色相是相同的,本质上它们表示的是同一种颜色值。
在这两种模型中,色相基于六大主色,按 60 度间隔排列在色环上。
这六大主色分别为:0°红色、60°黄色、120°绿色、180°青色、240°蓝色、300°洋红色,以及 360°回到红色。
在前端布局设计中,通常将色环展开为长方形色块,通过线性渐变进行分段处理,并将角度换算为相应的比例值,具体示意如下。

饱和度和明度/亮度的计算方式存在差异,为了便于理解颜色选择器的实现,本文对这两者进行简要概括。
- 在 HSV 模型中,饱和度(S)反映了色相中混入白色的程度,表现为从白色到色相(H)颜色的渐变效果;
- 在 HSL 模型中,饱和度(S)反映了色相中混入灰色的程度,表现为从灰色到色相(H)颜色的渐变效果。
- 在 HSV 模型中,明度(V)体现了从黑色到色相(H)颜色的过渡过程。
- 在 HSL 模型中,亮度(L)体现了从黑色到色相(H)颜色,再过渡到白色的过程。
此外,需明确各参数的取值范围:色相(Hue)为 0-360;饱和度和明度/亮度的取值范围均为 0-100%。
具体的代码实现细节,将在后续部分展开介绍。
实现选择器
色相
色相的原理前文已述,HSV 与 HSL 在此部分一致。在代码实现上,利用 CSS 和 div 元素即可轻松完成,核心在于应用颜色的线性渐变。
<div class="div-bar">
<!-- 色相柱 -->
<div id="colorBar" class="color-bar"></div>
<!-- 滑动块 -->
<div id="divSlider" class="div-slider"></div>
</div>.div-bar {
position: relative;
width: 10px;
height: 200px;
margin-left:15px;
cursor: pointer;
}
.color-bar {
background: linear-gradient(180deg, #f00, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00);
width: 10px;
height: 200px;
margin-left: 20px;
}
.div-slider {
...
width: 8px;
height: 8px;
}上述代码构建了一个竖向的色相柱,尺寸为宽 10 像素、高 200 像素。若需横向布局,调整宽高及 CSS 布局属性即可。
计算色相柱的滑块位置
要实现滑块移动并获取位置信息,首先需监听色相柱上的鼠标事件,具体代码如下。
colorBar.addEventListener('mousedown', function(e) {
setHueSlider(e)
document.addEventListener('mousemove', setHueSlider)
})
document.addEventListener('mouseup', function() {
document.removeEventListener('mousemove', setHueSlider)
})在 setHueSlider 函数中,主要处理滑块在色相柱上的位置计算,并通过比例换算得出对应的 hue 值。
const clientRect = colorBar.getBoundingClientRect()
// 获取位置信息,色柱高度-colorBarHEIGHT
let yDiff = event.clientY - clientRect.top
yDiff = yDiff > colorBarHEIGHT ? colorBarHEIGHT : (yDiff < 0 ? 0 : yDiff)
// 设置滑块的位置,防止滑块脱离色柱
const yTop = yDiff < 9 ? 0 : (yDiff - 9)
divSlider.style.top = yTop + 'px'
// 色相在360度以内,计算对应的比例值, 0 - 360
color.hue = Math.round((yDiff / colorBarHEIGHT) * 360 * 100) / 100 | 0
通过计算色相柱滑块的位置比例,即可得到对应的色相值。
饱和度和明亮度
除了色相柱,还需要构建基于饱和度和明度/亮度的颜色面板,此前关于两者区别的介绍在此处将发挥关键作用。
颜色面板通常是一个长方形区域。以 HSV 为例,前文提到饱和度可简化为从白色到当前色相颜色的变化。例如,若当前色相为红色,则饱和度表现为从白色到红色的渐变;而明度则体现从黑色到红色的变化。在设计面板时,我们定义一个长方形区域。
- 若将面板背景色设置为当前色相(如红色);
- 在水平方向设置饱和度,可通过白色到红色的线性渐变实现,即从左至右透明度逐渐降低(最左侧为白色,最右侧完全透明);
- 在垂直方向设置明度,可通过黑色到红色的线性渐变实现,即从下至上透明度逐渐降低(最底部为黑色,最顶部完全透明)。
如此即可简化 HSV 模型中的颜色面板,具体实现逻辑可通过下图理解。

当然,饱和度和明度在水平或垂直方向上的布局可以互换。
设置颜色面板时,使用 div 结合 CSS 即可。代码实现有多种方式,下文仅介绍使用单个 div 实现宽高 300x200 像素颜色面板的方法(其他方案包括使用两个 div 或伪类等)。
<div class="div-panel">
<!-- 颜色面板 -->
<div id ="colorPanel" class="color-panel" style="background-color: #f00;"></div>
<!-- 滑块 -->
<div id="divPicker" class="div-picker"></div>
</div>.div-panel {
position: relative;
width: 300px;
height: 200px;
cursor: pointer;
overflow: hidden;
}
.color-panel {
position: relative;
height: 200px;
width: 300px;
background: linear-gradient(to top, #000, transparent), linear-gradient(to right, #FFF, transparent);
}
.div-picker {
...
width: 12px;
height: 12px;
}计算这两个值
同样需要监听鼠标事件以获取位置信息。事件监听逻辑与色相部分一致,均涉及鼠标按下、移动和释放三个事件,此处省略具体代码。
直接展示饱和度和明度的计算逻辑。根据前文的布局定义,面板横向 X 轴对应饱和度,纵向 Y 轴对应明度,计算方式如下。
// 计算鼠标移动位置,面板宽度panelWIDTH,高度panelHEIGHT
const clientRect = colorPanel.getBoundingClientRect()
let yDiff = event.clientY - clientRect.top
let xDiff = event.clientX - clientRect.left
xDiff = xDiff > panelWIDTH ? panelWIDTH : (xDiff < 0 ? 0 : xDiff)
yDiff = yDiff > panelHEIGHT ? panelHEIGHT : (yDiff < 0 ? 0 : yDiff)
// 设置滑块位置,保证滑块至少一半在面板内
const yTop = yDiff - 6
const xLeft = xDiff - 6
divPicker.style.top = yTop + 'px'
divPicker.style.left = xLeft + 'px'
// 饱和度和明度,这里没有取百分比的值,后续转换时需要注意
color.saturation = Math.round(xDiff / panelWIDTH * 100)
color.value = Math.round((1 - yDiff / panelHEIGHT) * 100)
hsv 转 rgb
通过上述计算获取 HSV 的三个值后,即可将其转换为对应的 RGB 颜色值。
const hsvToRgb = function (hue, saturation, value) {
saturation = saturation * 255 / 100 | 0
value = value * 255 / 100 | 0
if (saturation === 0) {
return [value, value, value]
} else {
satVal = (255 - saturation) * value / 255 | 0
ligVal = (value - satVal) * (hue % 60) / 60 | 0
if (hue === 360) {
return [value, 0, 0]
} else if (hue < 60) {
return [value, satVal + ligVal, satVal]
} else if (hue < 120) {
return [value - ligVal, value, satVal]
} else if (hue < 180) {
return [satVal, value, satVal + ligVal]
} else if (hue < 240) {
return [satVal, value - ligVal, value]
} else if (hue < 300) {
return [satVal + ligVal, satVal, value]
} else if (hue < 360) {
return [value, satVal, value - ligVal]
} else {
return [0, 0, 0]
}
}
}透明度
在RGB\HSV\HSL三种颜色模型中,透明度均作为独立属性存在,不影响具体颜色的生成,因此可单独处理该值。若需支持透明度,布局上可与色相柱类似,使用长条形 div,通过计算位置比例得出当前选中的透明度值,布局示意如下。
<div class="div-alpha">
<!-- 透明柱状 -->
<div id="alphaBar" class="alpha-bar" style="linear-gradient(180deg, #f00, transparent)"></div>
<!-- 滑块 -->
<div id="divAlphaSlider" class="div-slider"></div>
</div> .div-alpha {
background-image: linear-gradient(
45deg,#c5c5c5 25%,transparent 0,transparent 75%,#c5c5c5 0,#c5c5c5),linear-gradient(
45deg,#c5c5c5 25%,transparent 0,transparent 75%,#c5c5c5 0,#c5c5c5);
}计算逻辑与色相柱类似,仅透明度取值范围为 0-100。
color.alpha = Math.round(100 - yDiff / alphaBarHEIGHT * 100) / 100
获取颜色值时,在 RGBA 或 HSLA 格式中直接加入透明度参数即可。
基于 HSL 的颜色选择器
前文已讲解 HSV 与 HSL 的区别,根据各自特性,实现 HSL 选择器仅需进行少量调整。
首先,色相(Hue)和透明度部分两者完全一致,无需任何改动。
其次,需调整饱和度和明度/亮度的颜色面板,HSL在布局时仅需修改部分 CSS 样式。
.color-panel {
position: relative;
height: 200px;
width: 300px;
/*横向X轴饱和度设置为灰的渐变,纵向Y轴设置为白到透明到黑*/
background: linear-gradient(to bottom, #fff 0%, transparent 50%, transparent 50%, #000 100%),
linear-gradient(to right, #808080 0%, transparent 100%);
}改变颜色面板后,获取 HSL 的色相、饱和度、亮度和透明度值的方式与 HSV 模式完全相同。
注意,此时获取的 HSLA 颜色若需用于 RGBA 场景,必须执行 HSL 到 RGB 的转换,相关转换函数详见颜色模型的基础知识。
综上所述,一个基础的颜色选择器组件即告完成。
使用canvas
除了基于 div 和 CSS 布局的颜色选择器外,我们还可以使用canvas来处理颜色选择器。
在布局层面,色相、面板和透明度均可使用 canvas 元素替代。由于 canvas 同样支持线性渐变,因此在色彩 UI 布局上没有任何障碍。
滑块的设计大致相同,仅需微调,因为使用 canvas 后,颜色获取方式发生了改变。
在 canvas 的色相和面板中,可直接获取对应位置的像素点,而该像素点本身即为一个 rgba 颜色值。
这一优势在于,我们可以省略转换过程,直接获取可用的颜色值。当然,无论是 div 还是 canvas,由于布局方式和颜色获取机制不同,所能得到的颜色值数量存在差异,特别是 canvas 方式,其精度与画布本身的像素点密度相关。
不过,两种方式均能提供数百万种颜色值,完全满足日常开发需求。
以上就是 JavaScript 快速实现一个颜色选择器的详细内容,更多关于 JavaScript 颜色选择器的资料请关注本站其它相关文章!