首页 > 其他分享 >颜色选择器的简单实现(附完整代码)

颜色选择器的简单实现(附完整代码)

时间:2024-10-29 12:51:28浏览次数:7  
标签:picker const color 代码 rgb alpha 颜色 pointer 选择器

颜色选择器的简单实现

使用渐变背景实现一个颜色选择器
image.png

关键知识点

  • HSV(Hue, Saturation, Value)
  • 使用渐变色实现色相选择器
  • 使用3个背景叠加实现Saturation(饱和度),Value(色调,纯度)的选择

关键代码

色相渐变背景

background: linear-gradient(180deg, red 0, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, red);

饱和度色调背景

.color-picker-saturation-mask-white {
        background: linear-gradient(90deg, #fff, transparent));
}
.color-picker-saturation-mask-black {
        background: linear-gradient(0deg, #000, transparent);
}

完整代码

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0" />
    <title>使用渐变背景实现一个颜色选择器</title>
    <meta
      name="keywords"
      content="demo, js, css, color-picker" />
    <style>
      body {
        background-color: #fff;

        --alpha-bg-size: 10px;
      }

      .alpha-bg {
        background-color: #fff;
        background-image: linear-gradient(45deg, lightgray 25%, transparent 25%, transparent 75%, lightgray 75%),
          linear-gradient(45deg, lightgray 25%, transparent 25%, transparent 75%, lightgray 75%);
        background-position: 0 0, calc(var(--alpha-bg-size) / 2) calc(var(--alpha-bg-size) / 2);
        background-size: var(--alpha-bg-size) var(--alpha-bg-size);
      }

      .flex {
        display: flex;
      }

      .color-picker-container {
        margin: 0 auto;
        padding: 0.5rem;
        max-width: 90vw;
        width: 400px;
        height: fit-content;
        border-radius: 0.25rem;
        background-color: #fff;
        box-shadow: 0 7px 30px rgba(100, 100, 111, 0.2);
      }

      .color-picker-saturation-wrap {
        position: relative;
        flex: 1;
        margin-right: 0.5rem;
        padding-bottom: calc(100% - var(--alpha-bg-size) * 2 - 0.5rem);
      }

      .color-picker-saturation-mask {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        cursor: pointer;
      }

      .color-picker-saturation-mask-white {
        background: linear-gradient(90deg, #fff, transparent);
      }

      .color-picker-saturation-mask-black {
        background: linear-gradient(0deg, #000, transparent);
      }

      .color-picker-saturation-pointer {
        position: absolute;
        top: 0;
        left: 100%;
        width: 4px;
        height: 4px;
        border-radius: 50%;
        box-shadow: 0 0 0 1.5px #fff, inset 0 0 1px rgba(0, 0, 0, 0.3), 0 0 1px 2px rgba(0, 0, 0, 0.4);
        cursor: pointer;
        transform: translate(-50%, -50%);
        pointer-events: none;
      }

      .color-picker-color-slider {
        position: relative;
        width: calc(var(--alpha-bg-size) * 2);
        border-radius: 0.125rem;
        background: linear-gradient(180deg, red 0, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, red);
        cursor: pointer;
      }

      .color-picker-alpha-slider-container {
        display: flex;
        margin-top: 0.5rem;
      }

      .color-picker-alpha-slider {
        position: relative;
        overflow-x: hidden;
        flex: 1;
        height: calc(var(--alpha-bg-size) * 2);
        border-radius: 0.125rem;
        cursor: pointer;
      }

      .color-picker-alpha-slider-gradient {
        width: 100%;
        height: 100%;
      }

      .color-picker-slider-pointer {
        position: absolute;
        border-radius: 1px;
        background: #fff;
        box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
        cursor: pointer;
        pointer-events: none;
      }

      .color-picker-alpha-slider-pointer {
        top: 1px;
        left: calc(100% - 4px);
        width: 4px;
        height: calc(100% - 2px);
      }

      .color-picker-color-slider-pointer {
        top: 0;
        left: 1px;
        width: calc(100% - 2px);
        height: 4px;
      }

      .color-picker-preview-container {
        margin-left: 0.5rem;
        width: calc(var(--alpha-bg-size) * 2);
        height: calc(var(--alpha-bg-size) * 2);
        border-radius: 0.125rem;
      }

      .color-picker-preview {
        width: 100%;
        height: 100%;
        border-radius: 0.125rem;
      }
    </style>
  </head>
  <body>
    <h1 style="text-align: center">使用渐变背景实现一个颜色选择器</h1>
    <div class="color-picker-container">
      <div class="flex">
        <div class="color-picker-saturation-wrap color-saturation-wrap">
          <div class="color-picker-saturation-mask color-picker-saturation-mask-white"></div>
          <div class="color-picker-saturation-mask color-picker-saturation-mask-black"></div>
          <div class="color-picker-saturation-pointer"></div>
        </div>
        <div class="color-picker-color-slider">
          <div class="color-picker-color-slider-pointer color-picker-slider-pointer"></div>
        </div>
      </div>
      <div class="color-picker-alpha-slider-container">
        <div class="color-picker-alpha-slider alpha-bg">
          <div class="color-picker-alpha-slider-gradient"></div>
          <div class="color-picker-alpha-slider-pointer color-picker-slider-pointer"></div>
        </div>
        <div class="color-picker-preview-container alpha-bg preview-container">
          <div class="color-picker-preview"></div>
        </div>
      </div>
    </div>
    <h2
      style="text-align: center"
      class="rgba-output"></h2>
    <h2
      style="text-align: center"
      class="hex-output"></h2>
    <h2
      style="text-align: center"
      class="hsv-output"></h2>

    <script>
      // 获取DOM元素
      const saturationWrap = document.querySelector('.color-saturation-wrap') // 饱和度包裹
      const colorSlider = document.querySelector('.color-picker-color-slider') // 颜色滑块
      const alphaSlider = document.querySelector('.color-picker-alpha-slider') // 透明度滑块
      const alphaSliderGradient = document.querySelector('.color-picker-alpha-slider-gradient') // 透明度渐变
      const colorPreview = document.querySelector('.preview-container .color-picker-preview') // 颜色预览
      const rgbaOutput = document.querySelector('.rgba-output') // 显示RGBA值
      const hexOutput = document.querySelector('.hex-output') // 显示Hex值
      const hsvOutput = document.querySelector('.hsv-output') // 显示HSV值
      const alphaSliderPointer = document.querySelector('.color-picker-alpha-slider-pointer') // 透明度指针
      const colorSliderPointer = document.querySelector('.color-picker-color-slider-pointer') // 颜色指针
      const saturationWrapPointer = document.querySelector('.color-picker-saturation-pointer') // 饱和度指针

      // 初始化色调、饱和度、亮度和透明度
      let hue = 0,
        saturation = 1,
        value = 1,
        alpha = 1

      // 限制数值在min和max之间
      const clamp = (value, min, max) => Math.min(Math.max(value, min), max)

      // 更新颜色预览及输出
      const updateColorPreview = () => {
        const rgb = hsvToRgb(hue, saturation, value) // 将HSV转换为RGB
        colorPreview.style.backgroundColor = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})` // 更新预览背景
        rgbaOutput.textContent = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})` // 更新RGBA输出
        hexOutput.textContent = rgbaToHex(rgb.r, rgb.g, rgb.b, alpha) // 更新Hex输出
        hsvOutput.textContent = `hsv(${hue}, ${Math.round(saturation * 100)}%, ${Math.round(value * 100)}%)` // 更新HSV输出
        updateAlphaSlider(rgb) // 更新透明度滑块
        return rgb // 返回RGB值
      }

      // 更新饱和度包裹的背景色
      const updateSaturationWrap = ({ r, g, b }) => {
        saturationWrap.style.backgroundColor = `rgb(${r}, ${g}, ${b})` // 使用当前RGB值更新背景
      }

      // 更新透明度滑块的背景渐变
      const updateAlphaSlider = ({ r, g, b }) => {
        alphaSliderGradient.style.background = `linear-gradient(to right, rgba(${r}, ${g}, ${b}, 0) 0%, rgba(${r}, ${g}, ${b}, 1) 100%)` // 从透明到不透明的渐变
      }

      // HSV 转 RGB
      const hsvToRgb = (h, s, v) => {
        let r, g, b
        const i = Math.floor(h / 60) % 6
        const f = h / 60 - Math.floor(h / 60)
        const p = v * (1 - s)
        const q = v * (1 - f * s)
        const t = v * (1 - (1 - f) * s)

        // 根据色相计算RGB值
        switch (i) {
          case 0:
            r = v
            g = t
            b = p
            break
          case 1:
            r = q
            g = v
            b = p
            break
          case 2:
            r = p
            g = v
            b = t
            break
          case 3:
            r = p
            g = q
            b = v
            break
          case 4:
            r = t
            g = p
            b = v
            break
          case 5:
            r = v
            g = p
            b = q
            break
        }

        return {
          r: Math.round(r * 255), // 将值缩放到0-255
          g: Math.round(g * 255),
          b: Math.round(b * 255)
        }
      }

      // RGBA 转 Hex
      const rgbaToHex = (r, g, b, a) => {
        const alpha = Math.round(a * 255) // 将alpha值转换为0-255
        return `#${((1 << 24) + (r << 16) + (g << 8) + b + (alpha << 24)).toString(16).slice(1)}` // 通过位运算生成Hex字符串
      }

      // 更新饱和度和亮度
      const updateSaturation = (event) => {
        const { offsetX, offsetY } = event // 获取鼠标相对位置
        const { width, height } = saturationWrap.getBoundingClientRect() // 获取包裹的宽高
        saturation = clamp(offsetX / width, 0, 1) // 计算饱和度
        value = clamp(1 - offsetY / height, 0, 1) // 计算亮度
        saturationWrapPointer.style.left = `${offsetX}px` // 更新指针位置
        saturationWrapPointer.style.top = `${offsetY}px`
        updateColorPreview() // 更新颜色预览
      }

      // 更新色调
      const updateHue = (event) => {
        const { offsetY } = event // 获取鼠标y坐标
        const { height } = colorSlider.getBoundingClientRect() // 获取颜色滑块高度
        hue = Math.round((offsetY / height) * 360) % 360 // 计算色调
        colorSliderPointer.style.top = `${offsetY}px` // 更新指针位置
        let rgb = updateColorPreview() // 更新预览并获取当前RGB
        updateSaturationWrap(rgb) // 更新饱和度背景
      }

      // 更新透明度
      const updateAlpha = (event) => {
        const { offsetX } = event // 获取鼠标X坐标
        const { width } = alphaSlider.getBoundingClientRect() // 获取透明度滑块宽度
        alpha = clamp(offsetX / width, 0, 1) // 计算透明度
        alphaSliderPointer.style.left = `${offsetX}px` // 更新指针位置
        updateColorPreview() // 更新颜色预览
      }

      // 事件监听器
      saturationWrap.addEventListener('click', updateSaturation) // 点击饱和度区域
      colorSlider.addEventListener('click', updateHue) // 点击色调滑块
      alphaSlider.addEventListener('click', updateAlpha) // 点击透明度滑块

      const init = () => {
        updateColorPreview() // 初始化颜色预览
        updateSaturationWrap({ r: 255, g: 0, b: 0 }) // 初始化饱和度包裹背景色
      }
      init() // 调用初始化函数
    </script>
  </body>
</html>

后续优化

  • 透明度和色相选择器的滑动选择
  • 颜色反向选择

标签:picker,const,color,代码,rgb,alpha,颜色,pointer,选择器
From: https://blog.csdn.net/jchsgwbr/article/details/143326158

相关文章

  • <项目代码>YOLOv8 煤矸石识别<目标检测>
      YOLOv8是一种单阶段(one-stage)检测算法,它将目标检测问题转化为一个回归问题,能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法(如FasterR-CNN),YOLOv8具有更高的检测速度和实时性。1.数据集介绍数据集详情可以参考博主写的文章<数据集>煤矸石识别......
  • 自动化测试工具Ranorex Studio(十五)-自定义代码ACTION
    在Recorder提供的功能不能够满足的情况下,可以使用自定义代码。这里有一些例子,可以方便演示自定义代码Action。•   自定义验证•   访问测试用例相关的数据和参数•   扩展报告在项目视图窗口中,仔细看一个录制模块文件,你会看到有两个相关的代码文件。 图:录......
  • onvif soap 协议的错误代码
    在stdsoap2.h头文件中定义的的宏#defineSOAP_EOFEOF#defineSOAP_OK0#defineSOAP_CLI_FAULT1#defineSOAP_SVR_FAULT2#defineSOAP_TAG_MISMATCH3#defineSOAP_TYPE......
  • 业务代码中先处理业务最后存储数据
    背景说明:在处理复杂业务的时候,特别是研发自测期间,经常会产生很多不必要的垃圾数据。技术原理:先将要存入数据库的数据放在缓存中,等所有业务代码执行完后,再统一保存;代码如下:@Slf4jpublicclassBaseService<MextendsBaseMapper<T>,T>extendsServiceImpl<M,T>implemen......
  • angular - 阅读zone.js代码引出的访问器属性问题
    对websocket的onmessageonerroronopenonclose事件是如何被zone.js代理存在疑问,阅读了zone.js的源码此处对WebSocket.prototype的onmessageonerroronopenonclose进行patch操作 具体的patch操作如下:  对WebSocket.prototype上的访问器属性(区别于数据属性)而言,webSo......
  • 代码随想录-栈与队列6、7
    逆波兰表达式思路用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中这里记录string类型相关操作:判断token是否是数字,不可像char类型用string重载的>=,<=,前者由于用ASCII码表示,后者按字典序比较,例如1<2所以字符串比较"10"<"2"。所以直接......
  • C语言:程序代码与文件的羁绊
    目录为什么使用文件文件认识程序文件数据文件文件名⼆进制文件和文本文件文件的打开和关闭流标准流文件指针文件操作符详解文件的操作:参数解释:文件操作符示例文件的打开与关闭读取文件字符串读取数据块写入数据块重定位文件指针获取指针位置文件指针移到开始处清除文......
  • 国家代码和国家地区代码有什么区别
    ​​国家代码和国家地区代码的区别主要体现在:1.定义及用途不同;2.格式和结构差异;3.颁发机构不同;4.应用范围有别;国家代码通常是ISO标准中定义的,如ISO3166-1中的两位或三位字母代码,而国家地区代码可能包括电话区号、邮政编码等,且格式更为多样。了解这些差异对于处理国际业务、软件......
  • ArkTS 应用的代码混淆策略:提升安全性与性能
    本文旨在深入探讨华为鸿蒙HarmonyOSNext系统(截止目前API12)的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。引言在移动应用开发领域,代码安全性一......
  • 多平台服务中的代码混淆与内存安全:ArkTS 应用的安全优化
    本文旨在深入探讨华为鸿蒙HarmonyOSNext系统(截止目前API12)的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。在开发跨平台应用时,代码安全与内存管......