取色器组件

139 阅读1分钟
<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">
			<!-- 取色器 START -->
			<view class="color-picker-wrap">
				
				<!-- 头部布局 START -->
				<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>
				<!-- 头部布局 END -->

				<!-- 取色板 START -->
				<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>
				<!-- 取色板 END -->

				<!-- 取色条 START -->
				<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>
				<!-- 取色条 END -->
				
				<!-- 透明度条 START -->
				<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>
				<!-- 透明度条 END -->
				
				<!-- 色块 START -->
				<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>
				<!-- 色块 END -->

			</view>
			<!-- 取色器 END -->
		</view>
	</view>
</template>

<script>
	export default {
		name: "color-picker",
		props: {
			value: {
				type: Boolean,
				default: false
			},
			color:{
				type:String,
				default:'#ff0000'
			}
		},
		data() {
			return {
				// ------------ popup START ------------
				visibleSync: false,
				showDrawer: false,
				closeFromInner: false,
				// ------------ popup END ------------
				
				// ------------ color-picker START ------------
				// hsb颜色值
				hsb: {
					h: 0,
					s: 100,
					b: 100
				},
				// rgb颜色值
				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, // 取色板X轴坐标
				paletteY:0, // 取色板Y轴坐标
				stripeX:0, // 取色条X轴坐标
				transparencyX:0, // 透明度条X轴坐标
				colorDotBar:null, // 取色点信息
				// ------------ color-picker END ------------
			};
		},
		watch: {
			value(val) {
				if (val) {
					this.open();
				} else if (!this.closeFromInner) {
					this.close();
				}
				this.closeFromInner = false;
			}
		},
		computed: {
			// ------------ popup START ------------
			// 遮盖层样式
			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%'})`
				}
			},
			// ------------ popup END ------------
			
			// ------------ color-picker START ------------
			// 色板背景色样式
			colorPaletteStyle() {
				return {
					backgroundColor: `rgb(${this.rgba.r},${this.rgba.g},${this.rgba.b})`
				}
			},
			// 取色板滑点XY坐标
			paletteDotStyle() {
				return {
					transform: `translate(${this.paletteX}px,${this.paletteY}px)`
				}
			},
			// 取色条滑点X坐标
			stripeDotStyle() {
				return {
					transform: `translateX(${this.stripeX}px)`
				}
			},
			// 透明度条滑点X坐标
			transparencyDotStyle() {
				return {
					transform: `translateX(${this.transparencyX}px)`
				}
			},
			// ------------ color-picker END ------------
		},
		mounted() {
			this.value && this.open();
		},
		methods: {

			// ------------ popup START ------------
			// 打开组件
			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) {
					// #ifdef H5 || MP
					setTimeout(() => {
						this[param2] = status;
					}, 50);
					// #endif
					// #ifndef H5 || MP
					this.$nextTick(() => {
						this[param2] = status;
					})
					// #endif
					!this.colorDotBar && setTimeout(() => {
						this.getColorDotBarElementInfo();
					}, 400);
				} else {
					setTimeout(() => {
						this[param2] = status;
					}, 250);
				}
			},
			// ------------ popup END ------------
			
			// ------------ color-picker STSRT ------------
			// 初始化
			colorPickerInit(hex){
				let color = hex || this.color || '#ff0000';
				// 判断 color 是哪一种颜色模式  hex || rgb || rgba
				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;
			},
			// 设置取色条滑点X轴位置
			setStrpeDotX(x) {
				this.stripeX = x;
			},
			// 设置透明度条滑点X轴位置
			setTransparencyDotX(x) {
				this.transparencyX = x;
			},
			// 设置XY位置
			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;
			},
			// 切换rgb颜色
			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);
				})
			},
			// hsb 转换为 rgb
			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)
				};
			},
			// rgb 转换为 hsb
			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;//事实上,max===min的时候,h无论为多少都无所谓
			    } 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}
			},
			// hex 转换为 rgba
			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;
			},
			// rgb 转换为 hex
			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();
				});
			},
			// ------------ color-picker END ------------
		}
	}
</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>