示例图
安装颜色转换算法插件
npm install color-convert
参考文章:hsv颜色选择器实现原理
取色器使用的是原生的EyeDropper,参考链接:MDN-EyeDropper
注: 此项功能仅在一些支持的浏览器的安全上下文(HTTPS)中可用。
vue组件代码
<template>
<div class="po">
<!-- 取色器 -->
<div class="eyedropper" @click="eyedropper">点击取色(自行替换图标)</div>
<!-- 色盘 -->
<div class="panel" ref="panel" @mousedown="panelMousedown">
<div class="white-panel"></div>
<div class="black-panel"></div>
<div
class="color-picker-panel-cursor"
:style="{
left: cursorPosition.x + 'px',
top: cursorPosition.y + 'px',
backgroundColor: `rgb(${cursorPosition.rgb.r}, ${cursorPosition.rgb.g}, ${cursorPosition.rgb.b})`,
}"
></div>
</div>
<!-- 色相 -->
<div class="color-slider is-vertical">
<div class="color-slider-bar" ref="hueSlider" @mousedown="hueMousedown">
<div ref="hueSliderThumb" class="color-slider-thumb"></div>
</div>
</div>
<!-- 透明度 -->
<div class="alpha-slider-bar">
<!-- 背景图 -->
<div ref="alphaSliderWrapper" class="alpha-slider-wrapper"></div>
<!-- 背景色 -->
<div class="alpha-slider-bg" @mousedown="alphaMousedown"></div>
<!-- 滑块元素 -->
<div ref="alphaSliderThumb" class="alpha-slider-thumb"></div>
</div>
<div
:style="{
width: '120px',
height: '36px',
background: `rgba(
${cursorPosition.rgb.r}
${cursorPosition.rgb.g},
${cursorPosition.rgb.b},
${alpha}
)`,
marginTop: '20px',
}"
></div>
<div>
<div>rgb: {{ cursorPosition.rgb }}</div>
<div>alpha: {{ alpha }}</div>
</div>
</div>
</template>
<script>
import convert from "color-convert";
export default {
data() {
return {
h: 0, // 色相
cursorPosition: {
x: 0, // 饱和度
y: 0, // 明值
rgb: { r: 0, g: 0, b: 0 },
},
alpha: 1,
};
},
mounted() {
console.log(convert);
let panel = this.$refs.panel;
let panelHeight = panel.offsetHeight;
this.cursorPosition.y = panelHeight;
},
methods: {
// 取色
eyedropper(e) {
// 需要在https并且浏览器支持的情况下才能运行
if (!window.EyeDropper) {
alert("当前浏览器不支持取色功能!!!");
// this.$message.error("当前浏览器不支持取色功能!!!");
return;
}
const dropper = new EyeDropper();
dropper
.open()
.then((result) => {
// hex 转 hsv
let hsv = convert.hex.hsv(result.sRGBHex);
// 设置色盘、色相、透明度值
this.alpha = 1;
this.$refs.alphaSliderThumb.style.left =
this.$refs.alphaSliderWrapper.offsetWidth + "px";
let hueSliderWidth = this.$refs.hueSlider.offsetWidth;
this.h = hsv[0]; // 色相值
let left = (hsv[0] / 360) * hueSliderWidth;
this.$refs.hueSliderThumb.style.left = left + "px";
// 计算色相值
this.computeHue(left);
let panel = this.$refs.panel;
this.cursorPosition.x = (hsv[1] / 100) * panel.offsetWidth;
this.cursorPosition.y = (-(hsv[2] - 100) / 100) * panel.offsetHeight;
this.getRgb(this.cursorPosition.x, this.cursorPosition.y);
})
.catch((e) => {
// this.$message.error("取色失败!!!");
alert("取色失败!!!");
});
},
panelMousedown(e) {
// 色盘元素
let panel = this.$refs.panel;
let panelWidth = panel.offsetWidth;
let panelHeight = panel.offsetHeight;
// mousedown设置第一次位置并转换rgb值
let x = e.pageX - panel.getBoundingClientRect().left;
let y = e.pageY - panel.getBoundingClientRect().top;
this.getRgb(x, y);
let self = this;
document.body.addEventListener("mousemove", panelMousemove);
function panelMousemove(e) {
// 更新光标位置和颜色值
const panelRect = panel.getBoundingClientRect();
let cursorX = e.clientX - panelRect.left;
// 边界判断
if (cursorX < 0) {
cursorX = 0;
} else if (cursorX > panelWidth) {
cursorX = panelWidth;
}
let cursorY = e.clientY - panelRect.top;
if (cursorY < 0) {
cursorY = 0;
} else if (cursorY > panelHeight) {
cursorY = panelHeight;
}
self.getRgb(cursorX, cursorY);
}
document.body.addEventListener("mouseup", (e) => {
document.body.removeEventListener("mousemove", panelMousemove);
});
},
// 获取rgb值
getRgb(x, y) {
let panel = this.$refs.panel;
let panelWidth = panel.offsetWidth;
let panelHeight = panel.offsetHeight;
let h = this.h;
let s = parseFloat(((x / panelWidth) * 100).toFixed(2));
let v = parseFloat(((-(y - panelHeight) / panelHeight) * 100).toFixed(2));
let rgbArr = convert.hsv.rgb(h, s, v);
this.cursorPosition = {
x,
y,
rgb: {
r: rgbArr[0],
g: rgbArr[1],
b: rgbArr[2],
},
};
},
// 色相滑动
hueMousedown(e) {
e.stopPropagation();
// 获取色相背景元素
let hueSlider = this.$refs.hueSlider;
// 获取色相滑块元素
let hueSliderThumb = this.$refs.hueSliderThumb;
// 鼠标按下设置初始值
hueSliderThumb.style.left = e.offsetX + "px";
this.computeHue(e.offsetX);
// 获取鼠标当前x轴位置
let mousePageX = e.pageX;
let sliderLeft = e.offsetX;
document.body.addEventListener("mousemove", hueMousemove);
document.body.addEventListener("mouseup", (e) => {
e.stopPropagation();
document.body.removeEventListener("mousemove", hueMousemove);
});
let self = this;
function hueMousemove(e) {
e.stopPropagation();
// 偏移量
let mouseOffsetX = e.pageX - mousePageX;
// left最终值 当前位置 + 偏移量
let left = sliderLeft + mouseOffsetX;
if (left < 0) {
left = 0;
} else if (left > hueSlider.offsetWidth) {
left = hueSlider.offsetWidth;
}
hueSliderThumb.style.left = left + "px";
self.computeHue(left);
}
},
// 计算色相值
computeHue(left) {
const sliderPercentage = left / this.$refs.hueSlider.clientWidth;
const h = Math.round(sliderPercentage * 360);
// 设置色相
this.h = h;
this.hue2rgb(h);
this.getRgb(this.cursorPosition.x, this.cursorPosition.y);
},
// hue to rgb
hue2rgb(h) {
let doHandle = (num) => {
if (num > 255) {
return 255;
} else if (num < 0) {
return 0;
} else {
return Math.round(num);
}
};
let hueRGB = (h / 60) * 255;
let r = doHandle(Math.abs(hueRGB - 765) - 255);
let g = doHandle(510 - Math.abs(hueRGB - 510));
let b = doHandle(510 - Math.abs(hueRGB - 1020));
let rgbString = "rgb(" + r + "," + g + "," + b + ")";
this.$refs.panel.style.backgroundColor = rgbString;
},
// 透明度
alphaMousedown(e) {
e.stopPropagation();
// 背景图元素
let alphaSliderWrapper = this.$refs.alphaSliderWrapper;
// 背景图宽度
let wrapperWidth = alphaSliderWrapper.offsetWidth;
// 滑块元素
let alphaSliderThumb = this.$refs.alphaSliderThumb;
// 设置滑块元素 left 值
alphaSliderThumb.style.left = e.offsetX + "px";
this.alpha = (e.offsetX / wrapperWidth).toFixed(2);
console.log(this.alpha);
// 获取鼠标当前x轴位置
let mousePageX = e.pageX;
let sliderLeft = e.offsetX;
document.body.addEventListener("mousemove", alphaMousemove);
document.body.addEventListener("mouseup", (e) => {
e.stopPropagation();
document.body.removeEventListener("mousemove", alphaMousemove);
});
let self = this;
function alphaMousemove(e) {
e.stopPropagation();
// 偏移量
let mouseOffsetX = e.pageX - mousePageX;
// left最终值 当前位置 + 偏移量
let left = sliderLeft + mouseOffsetX;
if (left < 0) {
left = 0;
} else if (left > wrapperWidth) {
left = wrapperWidth;
}
alphaSliderThumb.style.left = left + "px";
self.alpha = (left / wrapperWidth).toFixed(2);
}
},
},
};
</script>
<style lang="scss" scoped>
.po {
position: absolute;
top: 0;
left: 0;
width: 200%;
height: 100%;
background-color: #f5f5f5;
z-index: 99999;
padding: 20px;
}
.eyedropper {
cursor: pointer;
}
.panel {
width: 280px;
height: 180px;
position: relative;
// border: 1px solid #fff;
background-color: rgb(255, 38, 0);
// box-sizing: border-box;
& > div {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.white-panel {
background: linear-gradient(90deg, #fff, rgba(255, 255, 255, 0));
}
.black-panel {
background: linear-gradient(0deg, #000, transparent);
}
.color-picker-panel-cursor {
width: 6px;
height: 6px;
border-radius: 50%;
position: absolute;
// left: 100%;
// top: 0;
transform: translate(-50%, -50%);
box-shadow: 0 0 0 3px #fff, inset 0 0 2px 2px rgb(0 0 0 / 40%),
0 0 2px 3px rgb(0 0 0 / 50%);
background-color: transparent;
}
}
.color-slider,
.color-slider-bar {
position: relative;
}
.color-slider.is-vertical {
// width: 28px;
// height: 180px;
width: 280px;
height: 12px;
margin: 14px 0;
cursor: pointer;
}
.color-slider.is-vertical .color-slider-bar {
height: 100%;
border-radius: 6px;
background: linear-gradient(
90deg,
red 0,
#ff0 17%,
#0f0 33%,
#0ff 50%,
#00f 67%,
#f0f 83%,
red
);
}
.color-slider-thumb {
background-color: #fff;
border-radius: 4px;
position: absolute;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
border: 1px solid #dcdee2;
left: 0;
top: 0;
width: 5px;
height: 150%;
transform: translateY(-20%);
box-sizing: border-box;
position: absolute;
pointer-events: none;
}
// 透明度
.alpha-slider-bar {
width: 280px;
height: 12px;
position: relative;
cursor: pointer;
}
.alpha-slider-wrapper,
.alpha-slider-bg {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
border-radius: 6px;
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0),
rgba(255, 255, 255, 1)
);
}
.alpha-slider-bar.is-vertical .alpha-slider-bg {
/* 这里先暂时写死 */
background: linear-gradient(
to top,
rgba(255, 0, 0, 0) 0%,
rgba(255, 0, 0) 100%
);
}
.alpha-slider-wrapper {
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==");
}
.alpha-slider-thumb {
background-color: #fff;
border-radius: 4px;
position: absolute;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
border: 1px solid #dcdee2;
left: 100%;
top: 0;
width: 5px;
height: 150%;
transform: translateY(-20%);
box-sizing: border-box;
position: absolute;
pointer-events: none;
}
</style>