一起網易雲 🍉
網易雲音樂想必是大家很熟悉的一款 app 了,畢竟大家在深夜都會網抑雲
開玩笑了,最近在網易雲聽歌時,發現了一個很有意思的特效:
就是切換歌曲時,會根據當前封面替換背景色。作為資深切圖仔,我那該死的好奇心兜不住了,不行,我要去一探究竟。
首先我構思了很多它可能的實現方式:
- 機器學習對圖片進行色彩分析
- 前端提取圖片主色調,做漸變處理
- 封面背景圖做高斯模糊
對於第一種,他不在我的知識范圍內,這里就不展開說明了 😂。
第二種的話,一般都是利用canvas
來實現。
第三種相對來說,從技術層面來看,實現上是最為簡單的。
做了猜測分析后,我默默打開了熟悉的 Chrome 控制台,打開了網易雲音樂的源代碼:
好家伙,果然是第三種實現方式。🤐
本來到這里,本文就該結束了。但之前也有朋友問過我如何對前端圖片主題色進行提取
的問題,正好之前也做過類似的需求,這里就展開做個說明吧。
我們這里以一個圖片網站為例,來展示實際業務中應用較廣的場景:
在弱網下,圖片加載速度較慢,此時在圖片完全加載之前,提取圖片的主色調,然后填充為背景色。這樣用戶體驗能有較大的提升。
那具體是怎么實現的呢?🤔
我們這里采用canvas
來實現,具體分為三步:
- 獲取圖片數據
- 對圖片數據進行處理
- 對顏色列表排序
這里我們使用的測試圖片為:
相對來說,主色調較為明顯,也便於測試~
獲取圖片數據 🦊
我們知道圖片是由一個個像素點組成的。通過 canvas 的getImageData()
方法恰好可以獲取圖片的像素數據:
let imgObj = document.getElementById('yourId'); // 創建畫布 let canvas = document.createElement('canvas'); canvas.setAttribute('width', imgObj.width); canvas.setAttribute('height', imgObj.height); let context = canvas.getContext('2d'); // 將圖片畫在畫布上 context.drawImage(imgObj, 0, 0); // 獲取像素數據 let imgData = context.getImageData(0, 0, imgObj.width, imgObj.height); let pixelData = imgData.data;
但這時你去打印pixelData
,你會發現結果為:
好家伙,全是 0,,,😳
我一時想不到是什么原因:難道是 canvas 的 api 使用不熟練?
在
stackoverflow
上找到了上面的回答:
但是我修改后還是不行。
這時,我想到圖片加載是異步的。可能圖片還沒加載完畢就開始從畫布讀取圖片數據了,顯然這是不對的。於是我對原有代碼做了一番調整:
getMainColor("./test.jpeg"); function getMainColor(image) { return new Promise((resolve, reject) => { try { const canvas = document.createElement("canvas"); const img = new Image(); // 創建img元素 img.src = image; // 設置圖片源地址 img.onload = () => { let color = getImageColor(canvas, img); resolve(color); }; } catch (e) { reject(e); } }); } function getImageColor(canvas, img) { const context = canvas.getContext("2d"); context.drawImage(img, 0, 0); // 獲取像素數據 let pixelData = context.getImageData( 0, 0, canvas.width, canvas.height ).data; console.log("pixelData", pixelData); return pixelData; }
事實證明:it's true
獲取了圖片數據,下一步就要對其進行相應的處理。
對圖片數據進行處理 🦁
展開上一步得到的數據:
這里的數據是什么意思呢?其實就是rgba
,分布代表紅色(Red)
,綠色(Green)
,藍色(Blue)
和透明度(Alpha)
。rgba
的圖片每個像素點是由上面四個數值表示的。也就是說每四個為一組。
知道了規律,那讓我們來對數據做一下清洗:主要就是對顏色進行分組,並統計每種顏色分別出現的次數:
function getImageColor(canvas, img) { const context = canvas.getContext("2d"); context.drawImage(img, 0, 0); // 獲取像素數據 let pixelData = context.getImageData( 0, 0, canvas.width, canvas.height ).data; console.log("pixelData", pixelData); return getCountsArr(pixelData); } function getCountsArr(pixelData) { let colorList = []; let rgba = []; let rgbaStr = ""; // 分組循環 for (let i = 0; i < pixelData.length; i += 4) { rgba[0] = pixelData[i]; rgba[1] = pixelData[i + 1]; rgba[2] = pixelData[i + 2]; rgba[3] = pixelData[i + 3]; if (rgba.indexOf(undefined) !== -1 || pixelData[i + 3] === 0) { continue; } // console.log("rgba", rgba); rgbaStr = rgba.join(","); if (rgbaStr in colorList) { ++colorList[rgbaStr]; } else { colorList[rgbaStr] = 1; } } console.log("colorList", colorList); return colorList; }
打印colorList
結果為:
到這里,我們就得到了每種數據分別出現的次數。
對顏色列表排序 🐳
最后一步,對上面得到的色值對象做一個排序:
for (let prop in colorList) { arr.push({ // 如果只獲取rgb,則為`rgb(${prop})` color: `rgba(${prop})`, count: colorList[prop], }); } // 數組排序 arr.sort((a, b) => { return b.count - a.count; }); console.log("arr", arr);
排序后得到如下結果:
到這里我們就得到了圖片色值出現次數從大到小的排序數組,我們來看排在第一位的rgba(206,205,201,255)
:
再把測試圖片貼一下:
肉眼可見的主題色已經被提取出來了!🎉
反思 🚀
最后還是回到文章最開始提到的網易雲音樂的播放器特效。不管它的實現方式是怎么樣的,它的這種產品創意是值得我們學習的。
我們平時在瀏覽國內外的一些網站或者使用一些 app 時,總能遇到一些讓你拍手稱贊的效果。而這些特效往往又與我們前端分不開。
俗話說:前端是離產品最近的開發工程師
,那最近你有沒有遇到一些讓你感覺很驚艷或者很有想法的效果呢,歡迎在評論區留言 🎯
轉自https://mp.weixin.qq.com/s/6XgDWhIA-2jfacg49j6alA