uni-app开发小程序,根据图片提取主题色值

需求,在页面根据传入的图片提取图片主色值并用来设置区块背景色

javascript 复制代码
<template>
	<view class="icon-container">
		<view class="sport-icon" :style="{ backgroundColor: mainColor }">
			<image :src="'/static/images/sport/'+item.image" @load="handleImageLoad" class="li-img" mode="widthFix" />
		</view>
		<view class="sport-right">
			<view>
				<text class="sport-name">{{ item.name }}</text>
			</view>
			<view class="sport-text">{{ item.calorie }}千卡/{{ item.unit }}分钟</view>
		</view>
		<view class="align-self-end">
			<product-change :selected.sync="item.selected" @onChange="onChange" />
		</view>
		<!-- Canvas 2D画布(隐藏) -->
		<canvas type="2d" id="colorCanvas"
			style="position: absolute; left: -1000px; top: -1000px; width: 100px; height: 100px;"></canvas>
	</view>
</template>


<script>
	import productChange from './product-change.vue'
	export default {
		name: 'productItem',
		components: {
			productChange
		},
		props: {
			name: {
				type: String,
				default: ''
			},
			item: {
				type: Object,
				default: () => {}
			}
		},
		data() {
			return {
				imgUrl: '@/static/images/sport/icon-sport-default.png', // 示例图片
				mainColor: '#ffffff', // 初始背景色
				textColor: '#000000', // 文字颜色,根据背景色自动调整
				canvas: null, // Canvas实例
				ctx: null // Canvas 2D上下文
			};
		},
		mounted() {
			console.log(this.item)
			// 初始化Canvas 2D上下文(在组件挂载后获取)
			this.initCanvas();
		},
		methods: {
			onChange() {
				this.$emit('onChange', {
					name: this.name,
					item: this.item
				})
			},
			// 初始化Canvas 2D上下文
			initCanvas() {
				// 通过ID获取Canvas实例(兼容uni-app的获取方式)
				const query = uni.createSelectorQuery().in(this);
				query.select('#colorCanvas')
					.fields({
						node: true,
						size: true
					})
					.exec(res => {
						if (!res[0]) {
							console.error('未找到Canvas元素');
							return;
						}
						this.canvas = res[0].node;
						this.ctx = this.canvas.getContext('2d'); // 获取2D上下文
						// 设置Canvas尺寸(与样式尺寸一致)
						this.canvas.width = 100;
						this.canvas.height = 100;
					});
			},
			// 图片加载完成后触发
			handleImageLoad(e) {
				if (!this.canvas || !this.ctx) {
					console.error('Canvas未初始化完成');
					return;
				}
				const imgSrc = "/static/images/sport/" + this.item.image;
				// 获取图片信息(转为本地路径)
				uni.getImageInfo({
					src: imgSrc,
					success: (res) => this.drawToCanvas(res.path), // 绘制本地图片
					fail: (err) => {
						console.error('获取图片信息失败:', err);
						this.useDefaultColor();
					}
				});
			},
			// 绘制图片到Canvas(Canvas 2D方式)
			drawToCanvas(imagePath) {
				// 创建Image对象(Canvas 2D需要通过Image加载图片)
				const img = this.canvas.createImage();
				if (!imagePath.startsWith('/')) {
					imagePath = '/' + imagePath;
				}
				img.src = imagePath;
				// 图片加载完成后绘制到Canvas
				img.onload = () => {
					// 清空画布(避免残留旧内容)
					this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
					// 绘制图片(缩放到100x100,覆盖整个画布)
					this.ctx.drawImage(img, 0, 0, this.canvas.width, this.canvas.height);
					// 延迟100ms后获取数据(确保绘制完成)
					setTimeout(() => this.extractMainColor(), 100);
				};

				// 图片加载失败处理
				img.onerror = (err) => {
					console.error('图片绘制失败:', err);
					this.useDefaultColor();
				};
			},
			// 提取主色
			extractMainColor() {
				try {
					// 读取Canvas像素数据(Canvas 2D的getImageData)
					const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
					this.processImageData(imageData.data); // 处理像素数据
				} catch (err) {
					console.error('色值提取失败:{}', err);
				}
			},
			// 处理像素数据,计算主色
			processImageData(pixelData) {
				const colorFreq = {}; // 颜色出现频率
				let maxFreq = 0;
				let mainR = 255,
					mainG = 255,
					mainB = 255;

				// 遍历像素(每4个值为一组:R, G, B, A)
				for (let i = 0; i < pixelData.length; i += 4) {
					const r = pixelData[i];
					const g = pixelData[i + 1];
					const b = pixelData[i + 2];
					const a = pixelData[i + 3];

					// 忽略透明像素(透明度>50%的不统计)
					if (a < 128) continue;

					// 颜色量化(减少颜色种类,如每20阶合并一次)
					const key = `${Math.floor(r / 20)}-${Math.floor(g / 20)}-${Math.floor(b / 20)}`;
					colorFreq[key] = (colorFreq[key] || 0) + 1;

					// 记录出现频率最高的颜色
					if (colorFreq[key] > maxFreq) {
						maxFreq = colorFreq[key];
						mainR = r;
						mainG = g;
						mainB = b;
					}
				}

				// 设置主色和文字对比色
				this.mainColor = `rgb(${mainR}, ${mainG}, ${mainB},0.2)`;
				// 计算亮度(决定文字颜色)
				const luminance = (mainR * 299 + mainG * 587 + mainB * 114) / 1000;
				this.textColor = luminance > 130 ? '#000000' : '#ffffff';
			},
			// 使用默认颜色(失败时)
			useDefaultColor() {
				this.mainColor = '#f0f0f0';
				this.textColor = '#000000';
			}
		}
	}
</script>

<style lang="scss" scoped>
	.icon-container {
		border-bottom: 1px solid #F2F6FC;
		padding: 20rpx 40rpx;
		display: flex;
	}

	.li-img {
		// width: 55rpx;
		// height: 55rpx;
	}

	.sport-icon {
		width: 85rpx;
		height: 85rpx;
		padding: 15rpx;
		border-radius: 20rpx;
	}

	.sport-right {
		flex: 1;
		margin-left: 25rpx;
		width: 100%;
	}

	.sport-name {
		font-size: 32rpx;
	}

	.sport-text {
		color: #999;
		font-size: 26rpx;
	}

	.align-self-end {
		align-self: flex-end
	}

	.flex-end {
		display: flex;
		flex-direction: column;
		justify-content: flex-end;
	}
</style>
相关推荐
狂炫冰美式10 分钟前
人均配了AI, 为什么公司还是没变快? 🤔 本质还是分布式系统问题
前端·后端·架构
乘风gg1 小时前
多 Agent 不是万能的!搞懂这 5 个原则,少走 1 年弯路!
前端·agent·ai编程
猩猩程序员2 小时前
Vercel 推出 Agent 框架 Eve:让 AI Agent 像写 Web 应用一样简单
前端
爱读源码的大都督2 小时前
Claude Code源码分析(三):为什么系统提示词中需要有tools呢?
前端·人工智能·后端
爱勇宝2 小时前
Claude Code 被曝暗藏“隐形检测”代码:封代理不是最可怕的,可怕的是你根本不知道它在干什么
前端·后端·程序员
小牛不牛的程序员3 小时前
我用 Claude Code 半天撸完了一个完整网站,AI 编程到底提升了多少效率?
前端
东风破_3 小时前
JavaScript 面试常考的字符串算法:从反转字符串到回文判断
前端·javascript
ITOM运维行者3 小时前
从零搭建企业级服务器监控体系:踩坑实录与架构设计
前端·后端
monologues3 小时前
深入 Vue 3 源码:响应式系统的精妙设计与编译优化
前端
hunterandroid3 小时前
Paging 3 分页:从手动分页到声明式加载
前端