uni-app开发小程序,根据图片提取主题色值
需求,在页面根据传入的图片提取图片主色值并用来设置区块背景色
<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;// 图片加载完成后绘制到Canvasimg.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>