需求,在頁面根據傳入的圖片提取圖片主色值并用來設置區塊背景色
<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>