乐于分享
好东西不私藏

小程序(uniapp)实现颜色融合器

小程序(uniapp)实现颜色融合器

因为某些时候需要使用到某些颜色视觉的布局,但对颜色不敏感。所以想着实现个颜色融合器,让颜色融合达到对视觉对颜色变化的直接感受。
示例视频:

已关注

关注

重播 分享

一、功能
颜色选择器,参考:《uniapp小程序颜色选择器组件》

颜色融合,融合模式(线性混合、整叠底片等)
融合后颜色说明等
👆示例图👆
二、代码
1、布局说明
布局的选项栏中有,融合的颜色选择,融合模式选择组合可以融合成其它的颜色。
如颜色的线性融合代码示例:
//线性混合(取平均值)const blendAverage =(colors)=> {	  const total = colors.reduce((acc, color) => {	    acc.r += color.r;	    acc.g += color.g;	    acc.b += color.b;	    return acc;	  }, { r0g0b0 }); //计算数组元素之和	  const count = colors.length;	  return {	    rMath.round(total.r / count),	    gMath.round(total.g / count),	    bMath.round(total.b / count)	  }; //返回平均值	}
2、颜色转换,十六进制转换为RGB/RGB转换为16进制如代码:
// 工具函数,十六进制转RGBfunction hexToRgb(hex) {	  const bigint = parseInt(hex.slice(1), 16);	  return {	    r: (bigint >> 16) & 255,	    g: (bigint >> 8) & 255,	    bbigint & 255	  };	}// 工具函数,RGB转十六进制function rgbToHex(r, g, b) {	  return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();	}

三、示例代码
文中所用组件为颜色选择器,参考:《uniapp小程序颜色选择器组件》。完整的代码示例如下:
<template><viewclass="color-blender-box uni-column"><!--顶部结果--><viewclass="cbb-item cbb-top uni-column"v-if="blendResult.hex != ''"><labelclass="cbb-m-label">融合结果:</label><viewclass="cbb-it-item uni-row"><textclass="cbb-iti-res":style="setResultStyle">{{ blendResult.hex }}</text></view><viewclass="cbb-it-item uni-column"><labelclass="cbb-m-label">原始颜色:</label><viewclass="cbb-iti-info uni-column"><viewclass="cbb-iti-i-block uni-row"v-for="(color,index) in chooseColor":key="index"><textclass="cbb-iti-ib-color":style="'background-color:'+color+';'"></text><textclass="cbb-iti-ib-text">{{ '颜色'+(index+1) }}</text><textclass="cbb-iti-ib-text">{{ color }}</text><textclass="cbb-iti-ib-text">({{ colorHexToRgb(color) }})</text></view></view><labelclass="cbb-m-label">融合结果:</label><viewclass="cbb-iti-info uni-column"><viewclass="cbb-iti-i-block uni-row"><textclass="cbb-iti-ib-color":style="'background-color:'+blendResult.hex+';'"></text><textclass="cbb-iti-ib-text">颜色</text><textclass="cbb-iti-ib-text">{{ blendResult.hex }}</text><textclass="cbb-iti-ib-text">({{ colorHexToRgb(blendResult.hex) }})</text></view></view><labelclass="cbb-m-label">融合模式:</label><viewclass="cbb-iti-info uni-column"><viewclass="cbb-iti-i-block uni-column"><textclass="cbb-iti-ib-text">{{ currentMode.name }}</text><textclass="cbb-iti-ib-text">{{ currentMode.desc }}</text></view></view></view></view><!--中间内容--><viewclass="cbb-item cbb-mid uni-column"><labelclass="cbb-m-label">融合颜色:</label><viewclass="cbb-m-item uni-row"><viewclass="cbb-mi-colors uni-row"v-for="(color,index) in chooseColor":key="index"><viewclass="cbb-mic-block"><viewclass="cbb-micb-text" @tap="openColorPicker(index)":style="'background-color:'+color+';'">{{ color }}</view></view></view><ColorPickerref="colorsPicker":defaultColor="chooseColor[currentPicker]" @confirm="subColor" @cancel="cancel" /></view><labelclass="cbb-m-label">融合模式:</label><viewclass="cbb-m-item uni-row"><viewclass="cbb-mi-modeValue"v-for="(item,index) in modeArr":key="index"><text:class="['cbb-mim-text',item.id ==currentMode.id?'cbb-mim-ac':'cbb-mim-nor']"						@tap="changeMode(item)">{{ item.name }}</text></view></view><viewclass="cbb-m-tips"> {{ currentMode.desc }} </view></view><!--底部--><viewclass="cbb-item cbb-bottom uni-column"><viewclass="cbb-b-block uni-row"><textclass="cbb-bb-btn cbb-bb-reset" @tap="resetColors">重置</text><textclass="cbb-bb-btn cbb-bb-done" @tap="blendColors">颜色融合</text></view></view></view></template><scriptsetup>	import ColorPicker from "@/components/picker/ColorPicker.vue"	import {		onMounted,		ref,		nextTick,		computed	} from "vue";	//当前选中的模式	const currentMode = ref({		id: 1,		name: '线性混合',		desc: '线性混合: 所有颜色的RGB值取平均值'	}); //默认为线性混合	//融合模式	const modeArr = ref([{			id: 1,			name: '线性混合',			desc: '线性混合: 所有颜色的RGB值取平均值'		},		{			id: 2,			name: '正片叠底',			desc: '正片叠底: 模拟颜料混合效果,使颜色变暗'		},		{			id: 3,			name: '滤色',			desc: '滤色: 与正片叠底相反,使颜色变亮'		},		{			id: 4,			name: '叠加',			desc: '叠加: 结合乘法和屏幕模式,保持高光和阴影'		},		{			id: 5,			name: '变暗',			desc: '变暗: 比较所有颜色,选择最暗的值'		},		{			id: 6,			name: '变亮',			desc: '变亮: 比较所有颜色,选择最亮的值'		},	]);	//选择的颜色	const chooseColor = ref(['#FF0000', '#00FF00', '#0000FF']); //选择的颜色 3种 默认'#FF0000','#00FF00','#0000FF'	const currentPicker = ref(0); //当前选中的选择器 默认第一个	const colorsPicker = ref(null); //组件	const blendResult = ref({rgb:'',hex:''});//融合的结果	onMounted(() => {	});	//打开颜色选择器	const openColorPicker = (index) => {		currentPicker.value = index;		nextTick(() => {			colorsPicker.value.open();		})	};	// 颜色确认回调	const subColor = (e) => {		const {			hex,			rgba		} = e;		chooseColor.value.splice(currentPicker.value,1,hex);//更新选中的颜色值	};	const cancel = () => {		console.log('取消');	};	//选中融合模式	const changeMode = (obj) => {		currentMode.value = obj;	};	//设置结果的样式	const setResultStyle  = computed(() => {		const color = blendResult.value.hex;		let textColor = '#FFFFFF';		let border = "0rpx";		if(color == "#FFFFFF"){			textColor = "#000000";			border = "1rpx #dddddd solid";		}		return {			backgroundColor:color,			color:textColor,			border: border		};	});	//融合颜色	const blendColors =()=>{		const colors = chooseColor.value.map(item => hexToRgb(item)); //颜色转换为 RGB		let blended;		switch(currentMode.value.id){			case 2:			blended=blendMultiply(colors); //正片叠底			break;			case 3:			blended=blendScreen(colors); //滤色			break;			case 4:			blended=blendOverlay(colors); //叠加			break;			case 5:			blended=blendDarken(colors); //变暗			break;			case 6:			blended=blendLighten(colors); //变亮			break;			default:		    blended = blendAverage(colors); //线性混合		}		 const hex = rgbToHex(blended.r, blended.g, blended.b);		 console.log(hex,"融合得出的颜色")		 blendResult.value.hex = hex;		 blendResult.value.rgb = blended;	}	// 工具函数,RGB转十六进制	function rgbToHex(r, g, b) {	  return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();	}	// 工具函数,十六进制转RGB	function hexToRgb(hex) {	  const bigint = parseInt(hex.slice(1), 16);	  return {	    r: (bigint >> 16) & 255,	    g: (bigint >> 8) & 255,	    b: bigint & 255	  };	}	//线性混合(平均值)	const blendAverage =(colors)=> {	  const total = colors.reduce((acc, color) => {	    acc.r += color.r;	    acc.g += color.g;	    acc.b += color.b;	    return acc;	  }, { r: 0, g: 0, b: 0 });	  const count = colors.length;	  return {	    r: Math.round(total.r / count),	    g: Math.round(total.g / count),	    b: Math.round(total.b / count)	  };	}	//正片叠底	const blendMultiply =(colors)=> {	  if (colors.length === 0) return { r: 0, g: 0, b: 0 };	  let result = { ...colors[0] };	  for (let i = 1; i < colors.length; i++) {	    result.r = Math.round((result.r * colors[i].r) / 255);	    result.g = Math.round((result.g * colors[i].g) / 255);	    result.b = Math.round((result.b * colors[i].b) / 255);	  }	  return result;	}	//滤色	const blendScreen =(colors)=> {	  if (colors.length === 0) return { r: 255, g: 255, b: 255 };	  // 从第一个颜色开始	  let result = { ...colors[0] };	  // 对每个后续颜色应用滤色混合公式	  for (let i = 1; i < colors.length; i++) {	    // 滤色公式: 255 - ((255 - base) * (255 - blend)) / 255	    result.r = 255 - Math.round(((255 - result.r) * (255 - colors[i].r)) / 255);	    result.g = 255 - Math.round(((255 - result.g) * (255 - colors[i].g)) / 255);	    result.b = 255 - Math.round(((255 - result.b) * (255 - colors[i].b)) / 255);	  }	  return result;	}	//叠加	const blendOverlay =(colors)=> {	  if (colors.length === 0) return { r: 128, g: 128, b: 128 };	  let result = { ...colors[0] };	  for (let i = 1; i < colors.length; i++) {	    result.r = result.r < 128 	      ? Math.round((2 * result.r * colors[i].r) / 255)	      : 255 - Math.round((2 * (255 - result.r) * (255 - colors[i].r)) / 255);	    result.g = result.g < 128 	      ? Math.round((2 * result.g * colors[i].g) / 255)	      : 255 - Math.round((2 * (255 - result.g) * (255 - colors[i].g)) / 255);	    result.b = result.b < 128 	      ? Math.round((2 * result.b * colors[i].b) / 255)	      : 255 - Math.round((2 * (255 - result.b) * (255 - colors[i].b)) / 255);	  }	  return result;	}	//变暗	const blendDarken =(colors)=> {	  if (colors.length === 0) return { r: 255, g: 255, b: 255 };	  let result = { ...colors[0] };	  for (let i = 1; i < colors.length; i++) {	    result.r = Math.min(result.r, colors[i].r);	    result.g = Math.min(result.g, colors[i].g);	    result.b = Math.min(result.b, colors[i].b);	  }	  return result;	}	//变亮	const blendLighten =(colors)=> {	  if (colors.length === 0) return { r: 0, g: 0, b: 0 };	  let result = { ...colors[0] };	  for (let i = 1; i < colors.length; i++) {	    result.r = Math.max(result.r, colors[i].r);	    result.g = Math.max(result.g, colors[i].g);	    result.b = Math.max(result.b, colors[i].b);	  }	  return result;	}	//选中的颜色转换	function colorHexToRgb(hex) {	  const bigint = parseInt(hex.slice(1), 16);	  const r =  (bigint >> 16) & 255;	  const g =  (bigint >> 8) & 255;	  const b =  bigint & 255;	  const res = "R:"+r+", G:"+g+", B:"+b;	  return res;	}	//重置	const resetColors =()=>{		blendResult.value = {rgb:'',hex:''};		chooseColor.value = ['#FF0000', '#00FF00', '#0000FF'];		currentPicker.value = 0;		currentMode.value = {			id: 1,			name: '线性混合',			desc: '线性混合: 所有颜色的RGB值取平均值'		};	}</script><stylelang="scss">.color-blender-box {width100vw;height100vh;background-color#f1f1f1;overflow: hidden;align-items: center;	}.cbb-item {width90%;background-color#ffffff;border-radius10rpx;padding10rpx;overflow: hidden;margin-top20rpx;	}.cbb-top {flex2;justify-content: center;.cbb-it-item{flex-grow1;padding10rpx;.cbb-iti-res{width:100%;height150rpx;padding10rpx 0rpx;text-align: center;line-height150rpx;font-size1.2rem;font-weight: bold;border-radius10rpx;			}		}	}.cbb-mid {.cbb-m-item {padding10rpx;align-items: center;flex-wrap: wrap;.cbb-mi-colors {padding10rpx;.cbb-mic-block {color#ffffff;.cbb-micb-text {padding10rpx 20rpx;border-radius10rpx;					}				}			}.cbb-mi-modeValue {padding10rpx;margin-top20rpx;.cbb-mim-text {padding10rpx 20rpx;background-color#e0e7ff;border-radius10rpx;color#4f46e5;				}.cbb-mim-nor {border#ffffff 4rpx solid;				}.cbb-mim-ac {border#4f46e5 4rpx solid;				}			}		}.cbb-m-label {font-size1rem;font-weight: bold;color#333;padding-left20rpx;		}.cbb-m-tips {padding10rpx;font-size0.9rem;color#7c3aed;		}	}.cbb-bottom {height100rpx;margin-bottom20rpx;.cbb-b-block {padding10rpx;align-items: center;justify-content: center;.cbb-bb-btn {color: white;padding15rpx 20rpx;border-radius10rpx;font-size1.1rem;			}.cbb-bb-reset {background-color#d1d5db;margin-right50rpx;padding15rpx 40rpx;			}.cbb-bb-done {backgroundlinear-gradient(to right, #4f46e5#7c3aed);			}		}	}.cbb-iti-info{padding10rpx;.cbb-iti-i-block{align-items: center;.cbb-iti-ib-color{padding10rpx;height10rpx;width10rpx;			}.cbb-iti-ib-text{padding:10rpx;font-size0.9rem;color#333;			}		}	}</style>