JavaScript 原生实现颜色选择器:从原理到代码详解

作者:小算子

时间:2026-10-26

浏览:425

本文详细解析如何使用原生 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 颜色选择器的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章