需求,在页面根据传入的图片提取图片主色值并用来设置区块背景色
<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>