颜色选择器的简单实现
使用渐变背景实现一个颜色选择器
关键知识点
- 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>
后续优化
- 透明度和色相选择器的滑动选择
- 颜色反向选择