一起網易雲 ????
網易雲音樂想必是大家很熟悉的一款 app 了,畢竟大家在深夜都會
網抑雲
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5iZyMGM1IGOyYWMzgTYzEmY3cTMwADNlRTNyATN3U2Yw8CX1EzLchDMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLzM3Lc9CX6MHc0RHaiojIsJye.png)
開玩笑了,最近在網易雲聽歌時,發現了一個很有意思的特效:
就是切換歌曲時,會根據目前封面替換背景色。作為資深切圖仔,我那該死的好奇心兜不住了,不行,我要去一探究竟。
首先我構思了很多它可能的實作方式:
- 機器學習對圖檔進行色彩分析
- 前端提取圖檔主色調,做漸變處理
- 封面背景圖做高斯模糊
對于第一種,他不在我的知識範圍内,這裡就不展開說明了 ????。
第二種的話,一般都是利用
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 時,總能遇到一些讓你拍手稱贊的效果。而這些特效往往又與我們前端分不開。
俗話說:
前端是離産品最近的開發工程師
,那最近你有沒有遇到一些讓你感覺很驚豔或者很有想法的效果呢,歡迎在評論區留言