1.分析
该颜色选择器是通过HSV颜色模型实现的,默认值支持16进制和rgb格式。 ??HSV颜色模型是基于色相H(hue)、饱和度S(saturation)、明度V(Value)。 ????HSV中的H色相,反映HSV模型中的主色,色相是以六大主色为基础,分别按60度的间隔排列在圆环上。这六大主色分别是:0°红、60°黄、120°绿、180°青、240°蓝、300°洋红、360°红。 ????HSV中的S饱和度,反映色相颜色中混入白色的值,呈现白色到色相颜色的变化; ????HSV中的V明度,体现的是从黑色到色相(H)颜色的过渡。
??如图可以认为:面板的颜色主色为红色,越往左红色中混入的白色越多,直至全部为白色。越往下红色中混入的黑色越多,直至全为黑色。 ??色相柱实现: ????背景: background: linear-gradient(180deg,#f00,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,#f00); ????值([0,360))为:滑块的偏移量/ 色相柱的高度 )* 360 就是色相值,色相值通过转换公式可以得到对应的rgb颜色并用于颜色面板的背景色。 ??颜色面板实现: ????背景: background: linear-gradient(to top, #000, transparent),linear-gradient(to right, #fff, transparent); ????饱和度([0,100])值:offsetLeft / 颜色面板宽 * 100 ????亮度([0,100])值:offsetTop / 颜色面板高 * 100
2.实现
??效果如下
??代码如下
<template>
<div class="color-picker">
<div class="color-button">
<div class="back-ground">
<div class="contain" :style="{ backgroundColor: realShowColor }" @click="isShowDropDown">
<img style="height: 100%; width: 100%" src="./down.svg" alt="" />
</div>
</div>
</div>
<div :class="{ 'show-dropdown': isShow }" class="color-dropdown">
<div
ref="colorPannel"
class="color-pannel-box"
:style="{ backgroundColor: colorPannel.backgroundColor }"
>
<div
:style="{ top: colorPannel.top + 'px', left: colorPannel.left + 'px' }"
class="color-select-circle"
@mousedown="pannelMosueHandler($event)"
></div>
</div>
<div ref="colorBar" class="color-slider-box">
<div class="color-slider"></div>
<div
class="color-thumb"
:style="{ top: colorBar.top + 'px' }"
@mousedown="thumbMouseHandler($event)"
></div>
</div>
<div v-if="showAlpha" ref="alphaBar" class="color-alpha">
<div
class="color-alpha-bar"
:style="{
background: `linear-gradient(to right, ${alphaColorBar.barColor}, ${rgbToRgba(
alphaColorBar.barColor,
0
)})`
}"
></div>
<div
class="color-alpha-thumb"
:style="{ left: alphaColorBar.thumbLeft + 'px' }"
@mousedown="alphaBarMouseHandler($event)"
></div>
</div>
<div class="color-input">
<input v-model="realShowColor" class="color-input-box" type="text" />
<button @click="submitColor">确定</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ColorPicker',
props: {
showAlpha: {
type: Boolean,
default() {
return true
}
},
initColor: {
type: String,
default() {
return '#f00'
}
},
colorFormat: {
type: String,
default() {
return 'hex'
}
}
},
data() {
return {
colorConfig: {
h: 360,
s: 100,
v: 100,
alpha: 1,
value: '',
basicColor: ''
},
alphaColorBar: {
barColor: 'rgb(255, 0, 0)',
thumbLeft: 0,
width: 0
},
colorBar: {
top: 0,
height: 0
},
colorPannel: {
top: 0,
left: 300,
backgroundColor: '#f00',
height: 0,
width: 0
},
realShowColor: '#f00',
isShow: false,
isApply: false
}
},
mounted() {
this.initShowColor(this.initColor)
},
methods: {
initShowColor(color) {
let hsvObj, initRgb
if (color.indexOf('#') !== -1) {
initRgb = this.hexToRGB(color)
hsvObj = this.rgbToHSV(initRgb)
} else if (color.indexOf('rgb') !== -1) {
hsvObj = this.rgbToHSV(color)
} else {
throw new Error('初始化颜色格式错误,使用#fff或rgb格式')
}
if (hsvObj) {
this.colorConfig.h = hsvObj.h
this.colorConfig.s = hsvObj.s
this.colorConfig.v = hsvObj.v
}
this.colorBar.height = this.$refs.colorBar.getBoundingClientRect().height
this.colorPannel.height = this.$refs.colorPannel.getBoundingClientRect().height
this.colorPannel.width = this.$refs.colorPannel.getBoundingClientRect().width
if (this.showAlpha) {
this.alphaColorBar.width = this.$refs.alphaBar.getBoundingClientRect().width
this.alphaToPosition(this.colorConfig.alpha, this.alphaColorBar.width)
this.alphaColorBar.barColor = initRgb || color
}
this.colorPannel.backgroundColor = this.hueToRGB(this.colorConfig.h)
this.hsvToPosition(
this.colorConfig.s,
this.colorConfig.v,
this.colorPannel.width,
this.colorPannel.height
)
this.hueToPosition(this.colorConfig.h, this.colorBar.height)
this.colorForamtTransform()
this.realShowColor = this.colorConfig.value || this.initColor
},
isShowDropDown() {
this.isShow = !this.isShow
},
submitColor() {
let initColor
if (this.realShowColor.indexOf('rgba') !== -1) {
initColor = this.realShowColor.replace(/,\d{1,3}(?=\))/, '')
this.colorConfig.alpha = parseFloat(this.realShowColor.split(',')[3].replace(')', ''))
this.colorConfig.alpha = Math.max(0, this.colorConfig.alpha)
this.colorConfig.alpha = Math.min(this.colorConfig.alpha, 1)
} else {
initColor = this.realShowColor
}
this.initShowColor(initColor)
this.isShow = false
},
thumbMouseHandler(e) {
if (e.type === 'mousedown') {
document.body.addEventListener('mousemove', this.thumbMouseHandler)
document.body.addEventListener('mouseup', this.thumbMouseHandler)
} else if (e.type === 'mousemove') {
const elemInfo = this.$refs.colorBar.getBoundingClientRect()
this.colorBar.top = e.clientY - elemInfo.top
this.colorBar.top = Math.max(0, this.colorBar.top)
this.colorBar.top = Math.min(this.colorBar.top, elemInfo.height)
this.colorConfig.h = ((parseInt(this.colorBar.top) / elemInfo.height) * 360 * 100) / 100
if (this.colorConfig.h === 360) {
this.colorConfig.h = 0
}
this.colorPannel.backgroundColor = this.hueToRGB(this.colorConfig.h)
this.colorForamtTransform()
this.alphaColorBar.barColor = this.colorConfig.basicColor
this.realShowColor = this.colorConfig.value
} else if (e.type === 'mouseup') {
document.body.removeEventListener('mousemove', this.thumbMouseHandler)
document.body.removeEventListener('mouseup', this.thumbMouseHandler)
}
},
pannelMosueHandler(e) {
if (e.type === 'mousedown') {
document.body.addEventListener('mousemove', this.pannelMosueHandler)
document.body.addEventListener('mouseup', this.pannelMosueHandler)
} else if (e.type === 'mousemove') {
const elemInfo = this.$refs.colorPannel.getBoundingClientRect()
this.colorPannel.top = e.clientY - elemInfo.top
this.colorPannel.left = e.clientX - elemInfo.left
this.colorPannel.left = Math.max(0, this.colorPannel.left)
this.colorPannel.left = Math.min(this.colorPannel.left, elemInfo.width)
this.colorPannel.top = Math.max(0, this.colorPannel.top)
this.colorPannel.top = Math.min(this.colorPannel.top, elemInfo.height)
this.colorConfig.s = (parseInt(this.colorPannel.left) / elemInfo.width) * 100
this.colorConfig.v = (1 - parseInt(this.colorPannel.top) / elemInfo.height) * 100
this.colorForamtTransform()
this.alphaColorBar.barColor = this.colorConfig.basicColor
this.realShowColor = this.colorConfig.value
} else if (e.type === 'mouseup') {
document.body.removeEventListener('mousemove', this.pannelMosueHandler)
document.body.removeEventListener('mouseup', this.pannelMosueHandler)
}
},
alphaBarMouseHandler(e) {
if (e.type === 'mousedown') {
document.body.addEventListener('mousemove', this.alphaBarMouseHandler)
document.body.addEventListener('mouseup', this.alphaBarMouseHandler)
} else if (e.type === 'mousemove') {
const elemInfo = this.$refs.alphaBar.getBoundingClientRect()
this.alphaColorBar.thumbLeft = e.clientX - elemInfo.left
this.alphaColorBar.thumbLeft = Math.max(0, this.alphaColorBar.thumbLeft)
this.alphaColorBar.thumbLeft = Math.min(this.alphaColorBar.thumbLeft, elemInfo.width)
this.colorConfig.alpha = (1 - this.alphaColorBar.thumbLeft / elemInfo.width).toFixed(2)
this.colorForamtTransform()
this.realShowColor = this.colorConfig.value
} else if (e.type === 'mouseup') {
document.body.removeEventListener('mousemove', this.alphaBarMouseHandler)
document.body.removeEventListener('mouseup', this.alphaBarMouseHandler)
}
},
colorForamtTransform() {
if (this.showAlpha) {
this.colorConfig.basicColor = this.hsvToRGB(
this.colorConfig.h,
this.colorConfig.s,
this.colorConfig.v
)
this.colorConfig.value = this.rgbToRgba(this.colorConfig.basicColor, this.colorConfig.alpha)
} else {
if (this.colorFormat === 'hex') {
this.colorConfig.basicColor = this.hsvToRGB(
this.colorConfig.h,
this.colorConfig.s,
this.colorConfig.v
)
this.colorConfig.value = this.rgbToHex(this.colorConfig.basicColor)
}
if (this.colorFormat === 'rgb') {
this.colorConfig.basicColor = this.hsvToRGB(
this.colorConfig.h,
this.colorConfig.s,
this.colorConfig.v
)
this.colorConfig.value = this.colorConfig.basicColor
}
}
},
hueToRGB(h) {
if (h === 360) {
h = 0
}
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))
return 'rgb(' + r + ',' + g + ',' + b + ')'
},
hsvToRGB(h, s, v) {
s = s / 100
v = v / 100
let r = 0,
g = 0,
b = 0
let i, f, p, q, t
i = Math.floor(h / 60)
f = h / 60 - i
p = v * (1 - s)
q = v * (1 - f * s)
t = v * (1 - (1 - f) * s)
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 `rgb(${Math.round(r * 255)},${Math.round(g * 255)},${Math.round(b * 255)})`
},
rgbToHSV(rgbStr) {
let { r, g, b } = this.getRGB(rgbStr)
r = parseFloat(parseFloat(r / 255).toFixed(4))
g = parseFloat(parseFloat(g / 255).toFixed(4))
b = parseFloat(parseFloat(b / 255).toFixed(4))
const max = Math.max(r, g, b)
const min = Math.min(r, g, b)
let h, s
let v = max
const d = max - min
s = max === 0 ? 0 : d / max
if (max === min) {
h = 0
} else {
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0)
break
case g:
h = (b - r) / d + 2
break
case b:
h = (r - g) / d + 4
break
}
h /= 6
}
return { h: h * 360, s: s * 100, v: v * 100 }
},
hsvToPosition(s, v, width, height) {
this.colorPannel.top = height - (v * height) / 100
this.colorPannel.left = (s * width) / 100
},
hueToPosition(h, height) {
this.colorBar.top = (h * height) / 360
},
alphaToPosition(alpha, width) {
this.alphaColorBar.thumbLeft = (1 - alpha) * width
},
getRGB(rgbStr) {
const matchArr = rgbStr.match(/\(.+?\)/g)[0].match(/\w+/g)
let r = parseInt(matchArr[0])
let g = parseInt(matchArr[1])
let b = parseInt(matchArr[2])
return { r, g, b }
},
rgbToHex(rgbStr) {
let { r, g, b } = this.getRGB(rgbStr)
return `#${this.zeroFill(r.toString(16))}${this.zeroFill(g.toString(16))}${this.zeroFill(
b.toString(16)
)}`
},
rgbToRgba(rgbStr, alpha) {
return rgbStr.replace(')', `,${alpha})`)
},
hexToRGB(hexStr) {
if (hexStr.length === 4) {
const hexArr = hexStr.match(/\w{1}/g)
return `rgb(${parseInt(hexArr[0] + hexArr[0], 16)},${parseInt(
hexArr[1] + hexArr[1],
16
)},${parseInt(hexArr[2] + hexArr[2], 16)})`
}
if (hexStr.length === 7) {
const hexArr = hexStr.match(/\w{2}/g)
return `rgb(${parseInt(hexArr[0], 16)},${parseInt(hexArr[1], 16)},${parseInt(
hexArr[2],
16
)})`
}
},
zeroFill(val) {
return val.length > 1 ? val : '0' + val
}
}
}
</script>
<style lang="less" scoped>
.color-picker {
width: 500px;
margin: auto;
height: 500px;
margin-top: 200px;
position: relative;
.color-button {
height: 36px;
width: 36px;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 4px;
.back-ground {
height: 26px;
width: 26px;
margin: 4px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
.contain {
border: 1px solid rgba(0, 0, 0, 0.5);
border-radius: 2px;
}
}
}
.color-dropdown {
margin: auto;
width: 340px;
height: 0;
position: absolute;
top: 36px;
left: -154px;
background-color: rgba(0, 0, 0, 0.2);
display: flex;
flex-wrap: wrap;
transition: height 0.5s Ease-in;
align-content: space-evenly;
overflow: hidden;
.color-pannel-box {
position: relative;
width: 300px;
height: 192px;
margin-left: 10px;
background: linear-gradient(to top, #000, transparent),
linear-gradient(to right, #fff, transparent);
.color-select-circle {
position: absolute;
transform: translate(-4px, -4px);
border: 1px solid #fff;
width: 8px;
height: 8px;
border-radius: 50%;
}
}
.color-slider-box {
cursor: pointer;
width: 10px;
position: relative;
.color-slider {
background: linear-gradient(
180deg,
#f00,
#ff0 17%,
#0f0 33%,
#0ff 50%,
#00f 67%,
#f0f 83%,
#f00
);
width: 10px;
height: 192px;
margin-left: 10px;
}
.color-thumb {
width: 18px;
height: 7px;
position: absolute;
left: -3px;
transform: translate(0, -3px);
border-radius: 2px;
background-color: rgb(10, 10, 10);
border: 3px solid #fff;
margin-left: 9px;
}
}
.color-alpha {
position: relative;
height: 12px;
box-shadow: 2px 2px 2px 2px 2px rgba(0, 0, 0, 0.1);
margin-left: 10px;
width: 300px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
.color-alpha-bar {
height: 100%;
width: 100%;
}
.color-alpha-thumb {
width: 7px;
height: 18px;
position: absolute;
top: -3px;
transform: translate(-3px, 0);
border-radius: 2px;
background-color: rgb(10, 10, 10);
border: 3px solid #fff;
}
}
.color-input {
width: 100%;
padding: 0 10px;
margin: auto;
display: flex;
justify-content: space-between;
button {
color: #000;
}
.color-input-box {
color: #000;
border: 1px solid rgba(0, 0, 0, 0.1);
}
}
}
.show-dropdown {
height: 260px;
transition: height 0.3s Ease-in;
}
}
</style>
3.缺点
??不支持预览色。 ??不支持outSideClick,即点击颜色选择器容器外部动作未做处理。
|