<template>
<view class="popup-wrap" v-if="visibleSync" @touchmove.stop.prevent="">
<view class="mask-view" :class="{'mask-show':showDrawer}" :style="maskViewStyle" @click="close"></view>
<view class="content-view" :style="contentViewStyle">
<view class="color-picker-wrap">
<view class="color-header">
<view class="btn" @click="close">取消</view>
<view class="content">
<view class="bottom-bg">
<view class="color-block" :style="{backgroundColor:`rgba(${colorBlock.rgba.r},${colorBlock.rgba.g},${colorBlock.rgba.b},${colorBlock.rgba.a})`}"></view>
</view>
<view class="color-value">{{colorBlock.hex}}</view>
</view>
<view class="btn" @click="confirm">确认</view>
</view>
<view class="color-palette" :style="colorPaletteStyle">
<view class="mask-white">
<view
class="mask-black color-dot-bar"
@touchstart="touchstart($event,'0')"
@touchmove="touchmove($event,'0')"
@touchend="touchend($event,'0')"
>
<view class="palette-dot" :style="paletteDotStyle"></view>
</view>
</view>
</view>
<view
class="color-stripe color-dot-bar"
@touchstart="touchstart($event,'1')"
@touchmove="touchmove($event,'1')"
@touchend="touchend($event,'1')"
>
<view class="stripe-dot" :style="stripeDotStyle"></view>
</view>
<view class="bottom-bg">
<view
class="color-stripe transparency-stripe color-dot-bar"
@touchstart="touchstart($event,'2')"
@touchmove="touchmove($event,'2')"
@touchend="touchend($event,'2')"
>
<view class="stripe-dot" :style="transparencyDotStyle"></view>
</view>
</view>
<view class="color-block-wrap">
<view class="color-block-item" @click="colorBlockClick(item)" v-for="(item,index) in colorBlockList" :key="index" :style="{backgroundColor:item}"></view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: "color-picker",
props: {
value: {
type: Boolean,
default: false
},
color:{
type:String,
default:'#ff0000'
}
},
data() {
return {
visibleSync: false,
showDrawer: false,
closeFromInner: false,
hsb: {
h: 0,
s: 100,
b: 100
},
rgba: {
r: 255,
g: 0,
b: 0,
a: 1
},
colorBlock:{
rgba:{r: 255,g: 0,b: 0,a: 1},
hex:'#ff0000'
},
colorBlockList:[
'#ef5b9c','#00ae9d','#f391a9','#401c44','#fedcbd','#8f4b2e','#7fb80e','#ed1941','#444693','#45b97c',
'#694d9f','#f47920','#ef4136','#009ad6','#1b315e','#fdb933','#f05b72','#1d953f','#f36c21','#2a5caa',
],
paletteX:0,
paletteY:0,
stripeX:0,
transparencyX:0,
colorDotBar:null,
};
},
watch: {
value(val) {
if (val) {
this.open();
} else if (!this.closeFromInner) {
this.close();
}
this.closeFromInner = false;
}
},
computed: {
maskViewStyle() {
return {
backgroundColor: 'rgba(0, 0, 0, 0.5)',
zIndex: this.showDrawer ? 100 : -1,
transition: 'all 300ms ease-in-out'
};
},
contentViewStyle() {
return {
transform: `translateY(${this.showDrawer?'0':'100%'})`
}
},
colorPaletteStyle() {
return {
backgroundColor: `rgb(${this.rgba.r},${this.rgba.g},${this.rgba.b})`
}
},
paletteDotStyle() {
return {
transform: `translate(${this.paletteX}px,${this.paletteY}px)`
}
},
stripeDotStyle() {
return {
transform: `translateX(${this.stripeX}px)`
}
},
transparencyDotStyle() {
return {
transform: `translateX(${this.transparencyX}px)`
}
},
},
mounted() {
this.value && this.open();
},
methods: {
open() {
this.change('visibleSync', 'showDrawer', true);
},
close() {
this.closeFromInner = true;
this.change('showDrawer', 'visibleSync', false);
},
change(param1, param2, status) {
this.$emit('input', status);
this[param1] = status;
if (status) {
setTimeout(() => {
this[param2] = status;
}, 50);
this.$nextTick(() => {
this[param2] = status;
})
!this.colorDotBar && setTimeout(() => {
this.getColorDotBarElementInfo();
}, 400);
} else {
setTimeout(() => {
this[param2] = status;
}, 250);
}
},
colorPickerInit(hex){
let color = hex || this.color || '#ff0000';
if( /^#/.test(color) ){
this.rgba = this.hexToRgba(color);
this.setColorBlock(this.rgba,color);
}else if( /^(rgb|rgba)/.test(color) ){
let rgba = (color.match(/(?<=\()(.+?)(?=\))/g)||[''])[0].split(',');
if( rgba[0] === '' ){
rgba = [255,0,0,1];
}else{
rgba[3] = rgba[3] || 1;
for (let i = 0; i < rgba.length; i++) {
rgba[i] = rgba[i]*1||0;
}
}
this.rgba = {r:rgba[0],g:rgba[1],b:rgba[2],a:rgba[3]};
this.setColorBlock(this.rgba,this.rgbToHex(this.rgba));
}
this.hsb = this.rgbToHsb(this.rgba);
this.setPaletteDotXY(
this.hsb.s / 100 * this.colorDotBar[0].maxX,
this.colorDotBar[0].maxY - (this.hsb.b/100*this.colorDotBar[0].maxY),
);
this.setStrpeDotX(this.hsb.h / 360 * this.colorDotBar[1].maxX);
this.setTransparencyDotX(this.rgba.a * this.colorDotBar[2].maxX);
},
confirm(){
console.log({
hsb:this.hsb,
...this.colorBlock
});
this.$emit('confirm',{
hsb:this.hsb,
...this.colorBlock
});
this.close();
},
setColorBlock(rgba,hex){
this.colorBlock = {rgba,hex};
},
setPaletteDotXY(x, y) {
this.paletteX = x;
this.paletteY = y;
},
setStrpeDotX(x) {
this.stripeX = x;
},
setTransparencyDotX(x) {
this.transparencyX = x;
},
setPositionXY(x,y,i){
const {left,top,half,maxX,maxY} = this.colorDotBar[i];
x = Math.max(0, Math.min(x - left - half, maxX));
y = Math.max(0, Math.min(y - top - half, maxY));
if( i === '0' ){
this.setPaletteDotXY(x,y);
this.hsb.s = (x / maxX * 100).toFixed(0) * 1;
this.hsb.b = ((maxY - y) / maxY * 100).toFixed(0) * 1;
this.changeColor();
}else if( i === '1' ){
this.setStrpeDotX(x);
this.hsb.h = 360 * x / maxX;
this.changePaletteColor();
this.changeColor();
}else if( i === '2' ){
this.setTransparencyDotX(x);
this.rgba.a = (x / maxX).toFixed(1) * 1;
this.changeColor();
}
},
touchstart(e,i){
this.setPositionXY(e.changedTouches[0].clientX,e.changedTouches[0].clientY,i);
},
touchmove(e,i){
this.setPositionXY(e.changedTouches[0].clientX,e.changedTouches[0].clientY,i);
},
touchend(e,i){
this.setPositionXY(e.changedTouches[0].clientX,e.changedTouches[0].clientY,i);
},
colorBlockClick(hex){
this.colorPickerInit(hex);
},
changePaletteColor(){
const {r,g,b} = this.hsbToRgb({
h: this.hsb.h,
s: 100,
b: 100
});
this.rgba.r = r;
this.rgba.g = g;
this.rgba.b = b;
},
changeColor() {
let rgb = this.hsbToRgb(this.hsb);
this.setColorBlock({...rgb,a:this.rgba.a},this.rgbToHex(rgb));
},
getColorDotBarElementInfo(){
this.createSelectorQuery('.color-dot-bar')
.then(res=>{
let b1 = uni.upx2px(40);
let b2 = uni.upx2px(46);
res.forEach((item,index)=>{
item.barwh = index?b2:b1;
item.half = item.barwh / 2;
item.maxX = item.width - item.barwh;
item.maxY = item.height - item.barwh;
})
this.colorDotBar = res;
this.colorPickerInit();
})
.catch(err=>{
throw new Error('获取元素信息失败:',err);
})
},
hsbToRgb(hsb) {
var rgb = {};
var h = hsb.h;
var s = hsb.s * 255 / 100;
var v = hsb.b * 255 / 100;
if (s == 0) {
rgb.r = rgb.g = rgb.b = v;
} else {
var t1 = v;
var t2 = (255 - s) * v / 255;
var t3 = (t1 - t2) * (h % 60) / 60;
if (h === 360) h = 0;
if (h < 60) {
rgb.r = t1;
rgb.b = t2;
rgb.g = t2 + t3
} else if (h < 120) {
rgb.g = t1;
rgb.b = t2;
rgb.r = t1 - t3
} else if (h < 180) {
rgb.g = t1;
rgb.r = t2;
rgb.b = t2 + t3
} else if (h < 240) {
rgb.b = t1;
rgb.r = t2;
rgb.g = t1 - t3
} else if (h < 300) {
rgb.b = t1;
rgb.g = t2;
rgb.r = t2 + t3
} else if (h < 360) {
rgb.r = t1;
rgb.g = t2;
rgb.b = t1 - t3
} else {
rgb.r = 0;
rgb.g = 0;
rgb.b = 0
}
}
return {
r: Math.round(rgb.r),
g: Math.round(rgb.g),
b: Math.round(rgb.b)
};
},
rgbToHsb(rgb) {
const {r,g,b} = rgb;
var h = 0, s = 0, v = 0;
var max = Math.max(r,g,b);
var min = Math.min(r,g,b);
v = max / 255;
if (max === 0) {
s = 0;
} else {
s = 1 - (min / max);
}
if (max === min) {
h = 0;
} else if (max === r && g >= b) {
h = 60 * ((g - b) / (max - min)) + 0;
} else if (max === r && g < b) {
h = 60 * ((g - b) / (max - min)) + 360
} else if (max === g) {
h = 60 * ((b - r) / (max - min)) + 120
} else if (max === b) {
h = 60 * ((r - g) / (max - min)) + 240
}
h = parseInt(h);
s = parseInt(s * 100);
v = parseInt(v * 100);
return {h,s,b:v}
},
hexToRgba(hex) {
let shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function(m, r, g, b) {
return r + r + g + g + b + b;
});
let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
a: result[4] ? parseInt(result[4], 16) / 255 : 1,
} : null;
},
rgbToHex(rgb) {
let hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16)];
hex.map(function(str, i) {
if (str.length == 1) {
hex[i] = '0' + str;
}
});
return "#"+hex.join('');
},
createSelectorQuery(elName) {
return new Promise((resolve, reject) => {
uni.createSelectorQuery().in(this).selectAll(elName).boundingClientRect(data => {
if (data) {
resolve(data);
} else {
reject(data);
}
}).exec();
});
},
}
}
</script>
<style lang="scss" scoped>
.popup-wrap {
// 遮盖层 START
.mask-view {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 0;
transition: transform 0.3s;
&.mask-show {
opacity: 1;
}
}
// 遮盖层 END
// 弹窗 START
.content-view {
opacity: 1;
z-index: 1000;
position: fixed;
left: 0px;
right: 0px;
bottom: 0px;
padding-bottom: 0px;
background-color: transparent;
transition: -webkit-transform 300ms ease 0ms, transform 300ms ease 0ms;
transform-origin: 50% 50%;
.color-picker-wrap {
padding: 20rpx;
overflow: hidden;
background-color: #fff;
// 头部布局 STSRT
.color-header{
height: 60rpx;
margin-bottom: 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
.btn{
font-size: 30rpx;
color: #666;
}
.content{
display: flex;
align-items: center;
.bottom-bg{
width: 40rpx;
height: 40rpx;
background-color: #fff;
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
background-size: 20rpx 20rpx;
background-position: 0 0, 10rpx 10rpx;
margin-right: 20rpx;
border: 2rpx solid #333;
.color-block{
width: 100%;
height: 100%;
}
}
.color-value{
color: #333;
width: 140rpx;
}
}
}
// 头部布局 END
// 取色板 START
.color-palette {
height: 340rpx;
background-color: red;
position: relative;
.mask-white {
width: 100%;
height: 100%;
background: linear-gradient(to right, #ffffff, rgba(255, 255, 255, 0));
.mask-black {
width: 100%;
height: 100%;
background: linear-gradient(to top, #000000, rgba(0, 0, 0, 0));
}
}
.palette-dot {
width: 36rpx;
height: 36rpx;
border: 2rpx solid #fff;
border-radius: 50%;
background-color: rgba(#fff, 0.3);
}
}
// 取色板 END
// 取色条 START
.color-stripe {
height: 46rpx;
margin-top: 30rpx;
background-image: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
.stripe-dot {
width: 46rpx;
height: 46rpx;
background-color: #fff;
border-radius: 50%;
}
}
// 取色条 END
// 透明度条 START
.bottom-bg{
background-color: #fff;
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
background-size: 24rpx 24rpx;
background-position: 0 0, 12rpx 12rpx;
}
.transparency-stripe{
background: linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(0, 0, 0));
}
// 透明度条 END
// 色块 START
.color-block-wrap{
display: flex;
flex-wrap: wrap;
margin-top: 30rpx;
.color-block-item{
width: calc(100%/10);
height: 40rpx;
background-color: #333;
}
}
// 色块 END
}
}
// 弹窗 END
}
</style>